Below are a couple of templated 'business' types designed to represent compound and atomic states, respectively.
interface CompoundState<TName extends string, TChildren extends { [key: string]: AnyCompoundState | AnyAtomicState }> {
type: 'parent'
name: TName,
children: TChildren,
};
type AnyCompoundState = CompoundState<string, { [key: string]: AnyCompoundState | AnyAtomicState }>;
interface AtomicState<TName extends string> {
type: 'child',
name: TName,
}
type AnyAtomicState = AtomicState<string>;
In my application, these types will be composed to produce tree-like structures of compound and atomic states. Here is an example of such a type:
type MyStateChart = CompoundState<'cs0', {
cs1: CompoundState<'cs1', {
as1: AtomicState<'as1'>,
as2: AtomicState<'as2'>,
}>
}>;
What I would like to accomplish is to produce a union of tuples to represent possible 'paths' implied by the type MyStateChart
. Possible paths are tuples such as:
['cs0']
- A valid path for aCompoundState
may or may not traverse into children.['cs0', 'cs1']
- Same as above, we don't 'need' to traverse to leaf nodes.['cs0', 'cs1', 'as1']
- Full depth['cs0', 'cs1', 'as2']
- Full depth
In a (mostly) failed attempt at achieving this, I took two approaches:
Approach 1:
type PathA<TNode extends AnyCompoundState | AnyAtomicState> = TNode extends AnyCompoundState
? {
[K in keyof TNode['children']]: [TNode['name']] | [TNode['name'], PathA<TNode['children'][K]>]
}[keyof TNode['children']]
: [TNode['name']]
// Produces a type represented by nested tuple unions. I have been unable to 'flatten' this into distinct, fully-realized tuples
type TestPathA = PathA<MyStateChart>;
This produces a type like which is really close to what I want but that I'm unable to 'flatten':
type TestPathA = ["cs0"] | ["cs0", ["cs1"] | ["cs1", ["l1"]] | ["cs1", ["l2"]]]
Approach 2:
type Cons<H, T extends unknown[]> = ((h: H, ...tail: T) => unknown) extends ((...args: infer U) => unknown) ? U : never;
// Approach B: Approach that I hoped would work but complains with:
type PathB<TNode extends AnyCompoundState | AnyAtomicState> = TNode extends AnyCompoundState
? {
[K in keyof TNode['children']]: [TNode['name']] | Cons<TNode['name'], PathB<TNode['children'][K]>>
}[keyof TNode['children']]
: [TNode['name']]
type TestPathB = PathB<MyStateChart>;
This approach appears to be unbounded and the TypeScript compiler complains with:
"Type instantiation is excessively deep and possibly infinite.(2589)"
Can I achieve what I'm looking for? If so how?
: never };` and then `Paths>`. I'd be happy to write up an answer somewhere for delicious internet points, if you can point me to an actual question (since this one is not using a union)