Consider the following templates for 'business' types, representing compound and atomic states:
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 combined to form tree-like structures of compound and atomic states. Let's look at an example:
type MyStateChart = CompoundState<'cs0', {
cs1: CompoundState<'cs1', {
as1: AtomicState<'as1'>,
as2: AtomicState<'as2'>,
}>
}>;
I aim to create a union of tuples that represent possible 'paths' indicated by the MyStateChart
type. Examples of such paths include:
['cs0']
- A valid path where traversal into children is optional.['cs0', 'cs1']
- Similar to the previous one, bypassing leaf nodes is allowed.['cs0', 'cs1', 'as1']
- Full depth exploration.['cs0', 'cs1', 'as2']
- Full depth exploration.
I attempted two methods to achieve this:
Method 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']]
// Produced nested tuple unions. However, I couldn't flatten it into distinct tuples.
type TestPathA = PathA<MyStateChart>;
This approach yielded a close result but didn’t quite meet the desired outcome:
type TestPathA = ["cs0"] | ["cs0", ["cs1"] | ["cs1", ["l1"]] | ["cs1", ["l2"]]]
Method 2:
type Cons<H, T extends unknown[]> = ((h: H, ...tail: T) => unknown) extends ((...args: infer U) => unknown) ? U : never;
// Approach B failed with a complaint:
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 method seemed unbounded, leading to an error message from the TypeScript compiler:
"Type instantiation is excessively deep and possibly infinite.(2589)"
Is there a way to accomplish my goal? If so, how?
You can test out the code on the TypeScript Playground