The result of chaining methods is a generic object return type

My goal is to achieve the following:

let result = loader
    .add<number>(1)
    .add<string>("hello")
    .add<boolean>(true)
    .run();

I am seeking a method to create the hypothetical loader object in a way that automatically determines the TYPE of result as [number, string, boolean] without the need for manual declaration. Can this be accomplished in TypeScript?

Answer №1

Update: TypeScript 4.0 is set to introduce variadic tuple types, offering enhanced flexibility in manipulating built-in tuples. The Push<T, V> operation will now be simplified as [...T, V]. This results in a straightforward implementation as shown in the following code snippet:

type Loader<T extends any[]> = {
    add<V>(x: V): Loader<[...T, V]>;
    run(): T
}
declare const loader: Loader<[]>;

var result = loader.add(1).add("hello").add(true).run(); //[number, string, boolean]

Playground link


For TypeScript versions older than v4.0:

Unfortunately, there is no officially supported method in TypeScript to represent the operation of appending a type to the end of a tuple. This operation, called Push<T, V>, where T is a tuple and V is a value type, cannot be directly achieved. However, there is a way to prepend a value to the beginning of a tuple, known as Cons<V, T>. This is due to a feature introduced in TypeScript 3.0 to treat tuples as function parameter types. Additionally, the type Tail<T> can be used to extract the rest of the tuple after removing the first element (head):

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

In attempting to represent Push, there emerges a complicated recursive definition that is not feasible due to circular references. Although it is possible to work around this limitation, it is not recommended by the TypeScript team. Instead, limiting the tuple size to a fixed number (e.g. 9 or 10) and manually defining the Push operation for that size is a more practical approach:

type Push<T extends any[], V> = T['length'] extends 0 ? [V] : Cons<T[0], Push1<Tail<T>, V>>
...

By unrolling the recursive definition, a workaround can be implemented to achieve the desired tuple manipulation. This allows for adding elements to a tuple without exceeding the limitations imposed by TypeScript.


Equipped with the Push operation, a type definition for loader can be provided (implementation left to the user):

type Loader<T extends any[]> = {
  add<V>(x: V): Loader<Push<T, V>>;
  run(): T
}
declare const loader: Loader<[]>;

Testing the functionality:

var result = loader.add(1).add("hello").add(true).run(); //[number, string, boolean]

Successful operation is observed. Best of luck applying this workaround to achieve the desired tuple manipulation!


Update

Note that the previous solution relies on the --strictFunctionTypes compiler flag. An alternative definition of Push is provided below for scenarios where this flag cannot be used:

type PushTuple = ...
type Push<
    T extends any[],
    V,
    L = PushTuple[T['length']],
    P = { [K in keyof L]: K extends keyof T ? T[K] : V }
    > = P extends any[] ? P : never;

This definition leverages mapped tuples introduced in TypeScript 3.1, offering a concise solution for small tuple sizes. However, it incurs a quadratic growth in repetition for larger tuple sizes. The choice between the two definitions depends on the specific requirements and constraints of the project.

Choose the approach that best suits your needs. Good luck with your TypeScript endeavors!

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 display the input value from an on-screen keyboard in an Angular application?

https://i.sstatic.net/j76vM.pnghttps://i.sstatic.net/EQPZO.png I have included my code and output snippet below. Is there a better way to display the input value when clicking on the virtual keyboard? ...

NextJS applications can encounter issues with Jest's inability to parse SVG images

Upon running yarn test, an unexpected token error is encountered: Jest encountered an unexpected token This typically indicates that Jest is unable to parse the file being imported, suggesting it's not standard JavaScript. By default, Jest will use ...

What is the process for setting up a Quill Editor within an Angular 2 Component?

I am currently working on creating my own Quill editor component for my Angular 2 project. To integrate Quill into my project, I utilized npm for installation. My goal is to develop a word counter application using this component and I am referring to the ...

What are some tips for leveraging Angular input signals in Storybook?

I am currently working on setting up Storybook 8.0.8 with Angular 17.3. I have been using the Angular input() signal in my components, but I've encountered an interesting issue where the args for the storybook stories also need the argument type to be ...

Verify the completeness of data types within an array in typescript

I am currently developing a comprehensive match function that I want to ensure is exhaustive during compile time. Although adding a default case would help with this, I am intrigued by some interesting dependent typing techniques I have come across. It wou ...

Can an L1 VPC (CfnVpc) be transformed into an L2 VPC (IVpc)?

Due to the significant limitations of the Vpc construct, our team had to make a switch in our code to utilize CfnVpc in order to avoid having to dismantle the VPC every time we add or remove subnets. This transition has brought about various challenges... ...

Getting the data from the final day of every month in a Typescript time-series object array

I am dealing with timeseries data retrieved from an API that consists of random dates like the following: [ { "id": 1, "score": 23, "date": "2023-08-30" }, { "id": 2, "score&qu ...

Using TypeScript with Styled Components .attrs

I'm a bit perplexed about using the .attrs() function in conjunction with TypeScript. Let's consider the code snippet below: BottleBar.tsx: interface IBottleComponentProps { fill?: boolean } const BottleComponent = styled.div.attrs<IBottl ...

Keep an ear out for updates on object changes in Angular

One of my challenges involves a form that directly updates an object in the following manner: component.html <input type="text" [(ngModel)]="user.name" /> <input type="text" [(ngModel)]="user.surname" /> <input type="text" [(ngModel)]="use ...

Leveraging the Nest JS Validation Pipe in combination with the class-transformer to retrieve kebab-case query parameters

Can someone help me with using the Nest JS Validation Pipe to automatically transform and validate my GET Request Query Params? For example: {{url}}/path?param-one=value&param-two=value In my app.module.ts, I have included the following code to impl ...

What is the proper method for typing unidentified exports that are to be used in TypeScript through named imports?

Currently, I am developing an NPM package that takes the process.env, transforms it, and then exports the transformed environment for easier usage. The module is structured like this: const transformedEnv = transform(process.env) module.exports = transf ...

Issues encountered with Nextjs 13.4 and Next-Auth 4.2 regarding the signIn("credentials", { }); functionality not functioning as expected

When using next-auth credentials in my current project, I encountered an issue with the signIn() function from next-auth/react. It appears that the redirection to the admin page persists regardless of whether the login details are correct or not. {error: n ...

Importing Heroicons dynamically in Next.js for more flexibility

In my Next.js project, I decided to use heroicons but faced a challenge with dynamic imports. The current version does not support passing the icon name directly to the component, so I created my own workaround. // HeroIcon.tsx import * as SolidIcons from ...

React Native Async Storage: displaying a blank page issue

I am facing an issue while attempting to save my data locally using AsyncStorage, specifically with the getData method. const storeData = async (value: string) => { //storing data to local storage of the device try { await AsyncStorage.setItem(& ...

How can time duration be accurately represented in TypeScript?

As I work on designing an interface for my personal project, I am in need of adding a field that represents the length of time taken to complete a task. What data type and format should I use in TypeScript to express this? Below is the interface I have cr ...

Mastering Dropdown Navigation and Value Setting in Angular

I am facing a challenge with two components named ComponentA and ComponentB. In ComponentB, there is a link that, when clicked, should navigate to ComponentA and send specific data to it. This data needs to be displayed in the dropdown options of Component ...

Is there a way to check if a date of birth is valid using Regular Expression (RegExp) within a react form?

const dateRegex = new RegExp('/^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.] (19|20)\d\d+$/') if (!formData.dob || !dateRegex.test(formData.dob)) { formErrors.dob = "date of birth is required" ...

The data type returned by a method is determined by the optional parameter specified in the

I have a situation where I need to create a class with a constructor: class Sample<T> { constructor(private item: T, private list?: T[]) {} } and now I want to add a method called some that should return: Promise<T> if the parameter list ...

Issue encountered with dynamic ngStyle variable: ERROR Error: Unable to locate a supporting object 'ngStyleSmall'

I have defined two variables for ngstyle ngStyleSmall = { width: '150px !important', 'max-width': '150px', }; ngStylemedium = { width: '250px !important', 'max-width&apo ...

Using CamanJs in conjunction with Angular 6

Struggling to integrate camanjs with Angular 6? Wondering how to add the JavaScript library and use it within an Angular project when there are no types available on npm? Here are the steps I followed: First, install Caman using npm. Next, add it to ...