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

Is there a way to incorporate an "else" condition in a TypeScript implementation?

I am trying to add a condition for when there are no references, I want to display the message no data is available. Currently, I am working with ReactJS and TypeScript. How can I implement this check? <div className="overview-text"> < ...

What should be the datatype of props in a TypeScript functional HOC?

My expertise lies in creating functional HOCs to seamlessly integrate queries into components, catering to both functional and class-based components. Here is the code snippet I recently developed: const LISTS_QUERY = gql` query List { list { ...

In React + TypeScript, learn how to effectively pass down a function from a container component to a

I am currently working on developing a tabs application using React and Typescript. The main component, Tabs, manages the state and passes it down to the Content component. Additionally, I have implemented a function called 'handleName' which is ...

TypeScript compiler not processing recently generated files

While working on a project using TypeScript, I've noticed that the files compile without any issues when using tsc with the watch flag to monitor changes. However, I have run into an issue where when I create a new file, tsc does not automatically det ...

How can a particular route parameter in Vue3 with Typescript be used to retrieve an array of strings?

Encountered a build error: src/views/IndividualProgramView.vue:18:63 - error TS2345: Argument of type 'string | string[]' is not assignable to parameter of type 'string'. Type 'string[]' is not assignable to type 'strin ...

Error in Typescript Observable when using .startWith([])

I encountered the TypeScript error below: Error:(34, 20) TS2345: Argument of type 'undefined[]' is not assignable to parameter of type 'number | Scheduler'. Type 'undefined[]' is not assignable to type 'Scheduler& ...

Understanding how to retrieve the value count by comparing strings in JavaScript

In my array object, I am comparing each string and incrementing the value if one letter does not match. If three characters match with the string, then I increase the count value; otherwise, it remains 0. var obj = ["race", "sack", &qu ...

Changing a "boolean bit array" to a numerical value using Typescript

Asking for help with converting a "boolean bit array" to a number: const array: boolean[] = [false, true, false, true]; // 0101 Any ideas on how to achieve the number 5 from this? Appreciate any suggestions. Thanks! ...

Can you explain the distinction between the controls and get methods used with the FormGroup object?

I have encountered an interesting issue with 2 lines of code that essentially achieve the same outcome: this.data.affiliateLinkUrl = this.bookLinkForm.controls['affiliateLinkUrl'].value; this.data.affiliateLinkUrl = this.bookLinkForm.get(' ...

What causes Gun.js to generate duplicate messages within a ReactJs environment?

I need assistance with my React application where gun.js is implemented. The issue I am facing is that messages are being duplicated on every render and update. Can someone please review my code and help me figure out what's wrong? Here is the code s ...

Utilizing Rxjs to transform an array of objects

My goal is to map an array of objects. Currently, I have the following code: return this.service.post(url, payload, this.httpOptions) .pipe( map((obj: any, index) => [({ ...obj, val1: obj[index].val1.id, v ...

Leverage generic types and allow acceptance of objects with arbitrary keys

Is it possible to allow the Use function argument type to accept any unknown key, as well as correctly type the keys from SomeGeneric? function Example (opt: { valid?: boolean }) { } type SomeGeneric = Parameters<typeof Example>[0] function Use(op ...

Using Typescript to configure a custom proxy in a Create React App

I am looking to implement request proxying from a Create React App to a separate API server, with the ability to set the server dynamically or using environment variables. While I have followed the guide on manually configuring the proxy, I am encounteri ...

What is the purpose of the .default() method being added to the class constructor in transpiled Typescript code

In TypeScript, I have the following code snippet to create an instance of ConnectRoles Middleware in Express: let user = new ConnectRoles(config); The middleware initialization is expected to be a simple call to a constructor. However, after transpiling, ...

When transferring the code to the src folder, tRPC encounters issues and stops functioning

Currently, I am working on developing a basic Twitter clone using Next and TRPC. While tRPC is up and running smoothly, I am looking to streamline my code by consolidating it all within the src directory. However, upon moving everything, I encountered an i ...

Efficiently Combining Objects with Rxjs

I have a specific object structure that I am working with: const myObject = { info: [ { id: 'F', pronouns: 'hers' }, { id: 'M', pronouns: 'his'} ], items:[ { name: 'John', age: 35, ...

Encountering an issue: The type '{ children: Element; }' does not share any properties with type 'IntrinsicAttributes & CookieConsentProviderProps'

I recently updated my packages and now I'm encountering this error. Is there a way to resolve it without reverting back to the previous versions of the packages? I've come across similar errors reported by others, but none of the suggested solut ...

Leveraging Vue mixin within a @Component

After implementing the vue-property-decorator package, I encountered an issue when trying to use a mixin method inside the beforeRouteEnter hook. Here is what I initially tried: import { Component, Mixins } from 'vue-property-decorator'; import ...

After compiling, global variables in Vue.js 2 + Typescript may lose their values

I am currently working on a Vue.js 2 project that uses Typescript. I have declared two variables in the main.ts file that I need to access globally throughout my project: // ... Vue.prototype.$http = http; // This library is imported from another file and ...

Errors caused by Typescript transpilation only manifest on the production server

During the process of updating my node version and dependencies on both machines, I came across an issue where building my app in production on one machine resulted in an error, while building it on my main machine did not. I found that the errors disappe ...