So, if you really want to strongly type Branch, you need to give it a type corresponding to all the nested levels of children. That would look like a list or tuple of types. Since TypeScript 3.0 introduced tuple types in rest/spread expressions, you can kind of express this, but I don't know if it's worth it to you.
First, let's define the type functions Head and Tail which split a tuple type into its first element and a tuple of the rest of the elements:
type HeadTail<L extends any[]> =
((...args: L) => void) extends ((x: infer H, ...args: infer T) => void) ? [H,T] : never
type Head<L extends any[]> = HeadTail<L>[0];
// e.g., Head<[string, number, boolean]> is string
type Tail<L extends any[]> = HeadTail<L>[1];
// e.g., Tail<[string, number, boolean]> is [number, boolean]
Now we can define BranchInterface or Branch to take a tuple of types like this:
export interface BranchInterface<T extends any[]> {
children: Array<BranchInterface<Tail<T>>>
record: Head<T>;
}
export class Branch<T extends any[]> {
public children: Array<BranchInterface<Tail<T>>> = [];
constructor(public record: Head<T>) { }
}
Assuming you know that you want the top level to be an Office and the next level down to be a Contact, then you can define your list of types as [Office, Contact] and see if it works:
const rootBranch = new Branch<[Office, Contact]>(newOffice);
const anOffice = rootBranch.record; // Office
const aContact = rootBranch.children[0].record; // Contact
Of course if you traverse past that, you find out what Head<[]> is (that implementation gives {}, I guess):
const whoKnows = rootBranch.children[0].children[0].record; // {}
If you want to make the layers below Contact be something like never instead (because you will never traverse down that far), you can use a rest tuple like this:
const rootBranch = new Branch<[Office, Contact, ...never[]]>(newOffice);
const anOffice = rootBranch.record; // Office
const aContact = rootBranch.children[0].record; // Contact
const aNever = rootBranch.children[0].children[0].record; // never
const anotherNever = rootBranch.children[0].children[0].children[0].record; // never
Note that this requires you to explicitly specify the type parameter T when constructing a Branch, since the compiler cannot infer the type from the argument:
const oops = new Branch(newOffice);
oops.record; // any, not Office
Well, it works. Up to you if you want to go that way. Hope that helps; good luck!
Branch<unknown>orBranch<any>?anybecause I need to maintain the type. if the set is constructed asXthen you can't change toY. However here I don't want the children to construct as the same type as the parent. If that makes sense?