Create a tuple type that encompasses all possible paths within a recursive object configuration

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:

  1. ['cs0'] - A valid path where traversal into children is optional.
  2. ['cs0', 'cs1'] - Similar to the previous one, bypassing leaf nodes is allowed.
  3. ['cs0', 'cs1', 'as1'] - Full depth exploration.
  4. ['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

Answer №1

It was pointed out by @jcalz in his comment that the solution to this problem is similar to the approach used in the answer provided in another question.

Here is how the same method can be applied to solve the mentioned issue:

type Cons<H, T> = T extends readonly any[] ?
  ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never
  : never;

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]

type Paths<T extends AnyAtomicState | AnyCompoundState, D extends number = 10> = [D] extends [never] ? never : T extends AnyCompoundState ?
  { [K in keyof T['children']]-?: [T['name']] | (Paths<T['children'][K], Prev[D]> extends infer P ?
    P extends [] ? never : Cons<T['name'], P> : never
  ) }[keyof T['children']]
  : [T['name']];

type TestC = Paths<MyStateChart>;

The outcome of this implementation is as follows:

type TestC = ["cs0"] | ["cs0", "cs1"] | ["cs0", "cs1", "l1"] | ["cs0", "cs1", "l2"]

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can you surpass the type declarations of a module at the local level?

Is there a way to change the appearance of a specific typescript module for those importing it? I have webpack rules that modify the exports of this module during transpile time, which is why I want to override its appearance. In my custom.d.ts file, I h ...

The TypeScript namespace does not exist or cannot be located

Currently, I am working on coding in TypeScript. The specific code pertains to an Angular 2 application, but the main focus of my inquiry lies within TypeScript itself. Within my project, there are certain files that contain various models, such as the exa ...

I would like to customize the Primeng switch by changing the values from boolean to 'N' or 'Y'

I integrated the primeNg p-switch component into my Angular 2 project. By default, the input switch's values are boolean. However, I would like to have the values set to 'N' or 'Y' instead of true or false. @export class MyCompone ...

Using Typescript to implement a conditional return type and ensuring that the value types are consistent together

I am working with a useSelectedToggle hook that helps in connecting the UI state to the open/closed status of a dialog where it will be displayed. The toggle defines the value as (T) when it is open, and null when it is closed. How can I enforce stricter ...

How to achieve dynamic class instantiation through constructor injection in Angular 8?

Despite my efforts to find a solution, my understanding of Dependency Injection in services is still limited, making it challenging to get this thing working. I'm left wondering if there's any way to make it work at all. My current setup involve ...

I prefer not to run the next.js SWR until after the initial rendering

Development Setup ・ next.js ・ typescript ・ swr This uses swr for communication purposes. I am looking to only trigger it when the query value changes. However, it is also being executed during the initial rendering. How can I prevent it ...

Is it possible to access the service and 'self' directly from the HTML template?

When working with Angular 6, one method to access component properties from a service is to pass 'self' to the service directly from the component. An example of this implementation is shown below: myComponent.ts public myButton; constructor(p ...

Six Material-UI TextFields sharing a single label

Is there a way to create 6 MUI TextField components for entering 6 numbers separated by dots, all enclosed within one common label 'Code Number' inside a single FormControl? The issue here is that the label currently appears only in the first tex ...

Encountering a Typescript error when trying to pass a function as a prop that returns SX style

Imagine a scenario where a parent component needs to pass down a function to modify the styles of a reusable child component: const getStyleProps: StyleProps<Theme> = (theme: Theme) => ({ mt: 1, '.Custom-CSS-to-update': { padding ...

Obtaining the accurate return type based on the generic parameter in a generic function

Is there a way to determine the correct return type of a function that depends on a generic argument? function f1<T>(o: T) { return { a: o } } // How can we set T to number through (n: number)? type T1 = typeof f1 extends (n: number) => infe ...

Tips for displaying only the initial 15 characters of a text value

The content extracted from a .ts file is being displayed on the home.html page. I am aiming to display only the initial 15 characters followed by 3 dots (...). Despite my efforts, the following code snippet is not functioning as expected: home.html < ...

What is the proper way to add additional properties to an array object when initializing it in TypeScript?

Is there a more elegant solution for creating an object of type: type ArrayWithA = [number, number, number] & { a: string }; I accomplished this by: const obj : any = [1, 2, 3]; obj.a = "foo"; const arrayWithA : ArrayWithA = obj as ArrayWith ...

ESLint is notifying that the prop validation for ".map" is missing, with the error message "eslint react/prop-types" occurring in a Typescript React environment

Hey everyone, excited to be posting for the first time! Currently, I'm working on a small project using Typescript and React. I've run into an issue with ESLint where it doesn't recognize that a prop variable of type string[] should have a ...

Production environment experiencing issues with Custom Dashboard functionality for AdminJS

I have successfully integrated AdminJS into my Koa nodejs server and it works perfectly in my local environment. My objective is to override the Dashboard component, which I was able to do without any issues when not running in production mode. However, wh ...

What is the best approach to testing the React Hook "useEffect" that is used to make an API call with Typescript?

Currently, I am working on writing Jest-enzyme tests for a basic React application using Typescript along with the new React hooks. The main issue I am facing is with properly simulating the api call made within the useEffect hook. Within the useEffect, ...

The service method call does not occur synchronously

In my OrderServer class, I am utilizing an OrderService to connect to a database and retrieve data every minute. The communication with the web app is handled through SocketIO. Here is a snippet of the code: export class OrderServer { // some required fie ...

Ensuring data types for an array or rest parameter with multiple function arguments at once

I am looking for a way to store various functions that each take a single parameter, along with the argument for that parameter. So far, I have managed to implement type checking for one type of function at a time. However, I am seeking a solution that al ...

How to implement a toggle button in an Angular 2 application with TypeScript

Currently, I'm working with angular2 in conjunction with typescript. Does anyone know how to generate a toggle button using on - off?. ...

Include an external JavaScript file within a component to display pictures in a web browser using Angular 2

I am developing a website using Angular 2 and need to incorporate a JavaScript file into a component. The goal is for this script to adjust the height of certain images to match the height of the browser window. What is the best way to integrate this scri ...

The typescript error "Cannot read properties of undefined" is encountered while trying to access the 'map' function

I was attempting to follow a guide on creating an app using typescript and react, but I'm encountering an error that says "Cannot read properties of undefined (reading 'map')". I'm not sure why this is happening, can someone please offe ...