What is the best way to assign a type based on a variadic type in TypeScript?

TypeScript playground link

For my current project, I am designing a custom route handler creator for Express. The goal is to allow passing arbitrary assertions as initial arguments before invoking the route handler callback. Here's an example of how I envision it:

const myHandler = makeHandler(assertion1(), assertion2(), (data, req, res, next) => {
  // data contains results from the assertions
});

I have managed to achieve some progress in defining the types as needed:

// declaration of Assertion and ReturnTypes

type Assertion<T = unknown> = (req: express.Request) => T;
type ReturnTypes<A extends ReadonlyArray<Assertion>> = {
  [K in keyof A]: ReturnType<A[K]>;
};

function assertion1<T extends object>(arg: T) {
  return () => arg
}

function assertion2() {
  return () => "yes"
}

const a = assertion1({ something: "yes" })
const b = assertion2()

// The expected type here is [{ something: string }, string]
type d = ReturnTypes<[typeof a, typeof b]>

However, when attempting to extend this concept to the arguments of makeHandler, there seems to be an issue where the type of data ends up being unknown[]:

// logic for `makeHandler`

declare function makeHandler<
 Assertions extends Assertion<unknown>[]
>(...assertionsAndCb: [...Assertions, HandlerCb<ReturnTypes<Assertions>>]): void

// 'data' isn't correctly typed here. It should match the same type as `d` above.
makeHandler(assertion1({ hey: "what"}), assertion2(), (data, req) => {
  return { response: {} }
})

I've tried researching similar concepts like zip to improve my function, but I'm struggling to get the types to pass accurately. Is there a missing element or incorrect generic that is preventing proper inference?

Answer №1

A key challenge here lies in the limitations posed by TypeScript when it comes to inferring both generic type arguments and contextual types for callback parameters simultaneously, especially in cases where they seem to be interdependently circular. The ongoing issue can be found at microsoft/TypeScript#47599. While efforts have been made to address these issues (check out microsoft/TypeScript#48538), it is unlikely that a complete resolution will be achieved since TypeScript's inference algorithm doesn't aim to mirror a full unification algorithm. Perhaps future updates may bring about improvements that align better with your intended code behavior, but until then, workarounds are necessary.

In this particular scenario, it appears that you are attempting to achieve simultaneous generic and contextual typing within a tuple structure featuring a leading rest element. Some oddities and challenges have emerged in such contexts, illustrated by the example highlighted in microsoft/TypeScript#47487. Although the mentioned issue has been resolved in TypeScript 5.1, it may not directly correspond to your situation. You might consider submitting a bug report or feature request to potentially address similar concerns effectively.


For the provided example, a potential strategy could involve exploring the implementation of a "reverse mapped type", whereby a generic function utilizes a homomorphic mapped type as the parameter type. By following this approach, you can infer the generic type parameter T from {[K in keyof T]: F<T[K]>}, as demonstrated below:

declare function makeHandler<T extends any[]>(
  ...a: [...{ [I in keyof T]: Assertion<T[I]> }, HandlerCb<T>]
): void;

This setup facilitates the easier inference of T, representing the tuple of return types from your Assertions. Consequently, the inference for HandlerCb<T> becomes more straightforward as well. The modified code snippet should yield the desired inference outcomes.

// Additional examples and explanations included within the original text

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

Cannot perform table inserts or creates with NestJS Sequelize functionality

I am currently in the process of setting up a small web server with a MySQL database. To achieve this, I am utilizing NestJs along with Sequelize. However, as I am still in the learning phase, I seem to be encountering an error: Within my database, I have ...

Creating a custom data type for the Tanstack table filtering function

I developed a unique filter function specifically for enhancing the functionality of Tanstack Table by utilizing fuse.js. Despite my efforts, TypeScript consistently raises concerns when I try to define the type for my custom function. My goal is to alig ...

Eliminating null values from a multidimensional array

Is there a way to remove the array elements cctype, cctypologycode, and amount if they are empty? What would be the most efficient approach? { "ccInput": [ { "designSummaryId": 6, "CCType": "A", "CCTypologyCode": "A", "Amount ...

Angular Http Promise is not returning the expected value

Struggling to update my component property with an HTTP result, but encountering issues. Thank you for your assistance! (currently using a static mock object) Class - Object export class Gallery { name: string; } Service import { Injectable } from ...

The user interface design transforms as a PDF file is being generated through html2pdf

I am experiencing an unusual problem while using html2pdf to convert an HTML page to a PDF file and download it. The conversion process is successful and the PDF file is downloaded without any issues. However, when I click on a button to generate the file, ...

Testing a Mocha import for a class from an unexported namespace

I'm in the process of creating unit tests for my Typescript application using the Mocha test framework. Within my web app, I have an internal module (A) that contains a class B. namespace A { export class B { constructor() { } ...

Error: The function $compile does not exist

Currently, I am working on developing an AngularJS directive using TypeScript. While testing my code in the browser, I encountered the following error: TypeError: $compile is not a function at compileComponent.js:14 Interestingly, the TypeScript compiler ...

What is the best way to import and export modules in Node.js when the module names and directories are given as strings?

Here is the structure of my folder: modules module-and index.js module-not index.js module-or index.js module-xor index.js moduleBundler.js The file I'm currently working on, moduleBundler.js, is re ...

The Angular material slider experiences issues with functionality when paired with the *ngFor directive

Having a unique problem that I could easily replicate on stackblitz. When using multiple mat sliders generated from a *ngFor loop with numbers as values, encountering an issue where moving the first slider affects all others. Subsequent drags only update ...

What is the best way to search for a specific value in the Record definition?

In the documentation for Typescript, a type is defined to be used as keys into a Record<>. It seems like this is done to restrict and secure the keys that can be utilized. type CatName = "miffy" | "boris" | "mordred"; W ...

Angular - How to fix the issue of Async pipe not updating the View after AfterViewInit emits a new value

I have a straightforward component that contains a BehaviorSubject. Within my template, I utilize the async pipe to display the most recent value from the BehaviorSubject. When the value is emitted during the OnInit lifecycle hook, the view updates correc ...

Implementation of a recursive stream in fp-ts for paginated API with lazy evaluation

My objective involves making requests to an API for transactions and saving them to a database. The API response is paginated, so I need to read each page and save the transactions in batches. After one request/response cycle, I aim to process the data an ...

Develop a custom function in Typescript that resolves and returns the values from multiple other functions

Is there a simple solution to my dilemma? I'm attempting to develop a function that gathers the outcomes of multiple functions into an array. TypeScript seems to be raising objections. How can I correctly modify this function? const func = (x:number, ...

Tips for exporting/importing only a type definition in TypeScript:

Is it possible to export and import a type definition separately from the module in question? In Flowtype, achieving this can be done by having the file sub.js export the type myType with export type myType = {id: number};, and then in the file main.js, i ...

Is it possible for a class method in Typescript to act as a decorator for another method within the same

Can we implement a solution like this? class A { private mySecretNumber = 2; decorate (f: (x :number) => number) { return (x: number) => f(this.mySecretNumber * x); } @(this.decorate) method (x: number) { return x + 1; } } I h ...

Firebase Promise not running as expected

Here is a method that I am having trouble with: async signinUser(email: string, password: string) { return firebase.auth().signInWithEmailAndPassword(email, password) .then( response => { console.log(response); ...

TS: How can we determine the type of the returned object based on the argument property?

Assume we have the following data types type ALL = 'AA' | 'BB' | 'CC'; type AA = { a: number; }; type BB = { b: string; }; type CC = { c: boolean; }; type MyArg = { type: ALL }; I attempted to create a mapping between type n ...

Adding Images Using Angular 8

I'm encountering difficulties with image upload in the file located at '../src/app/assets/'. Below is the Form I am using: <form [formGroup]="formRegister" novalidate=""> <div class="form-group"> <label for="ex ...

How can I specifically activate the keydown event for alphanumeric and special characters in Angular7?

I am looking to create a keydown event that will be triggered by alphanumeric or special characters like #$@. <input type="text" style="width: 70%;" [(ngModel)]= "textMessage" (keydown) ="sendTypingEvent()" > However, I want to prevent the event ...

Is there a way to view the type signature of the resulting intersection type (type C = A & B) in IDE hints, rather than just seeing the components?

When analyzing types defined by intersection in Typescript, I notice that the hint remains identical to the original definition: https://i.stack.imgur.com/mjvI8.png However, what I actually want is to visualize the resulting shape, similar to this: http ...