Home > Blockchain >  How to create a Mapped Type in TypeScript with variable generic arguments?
How to create a Mapped Type in TypeScript with variable generic arguments?

Time:08-08

I have an object containing one or more PreparedStatement. I need to generalize the type definition for such object. Is it possible to represent that?

type PreparedQuery<Input, Result> = {
  input: Input;
  result: Result;
};

type Query = {
  [Key in string]: PreparedQuery<Input, Result>;
};

Each value of the Query type of object would be PreparedQuery but with different Input and/or Result. The main goal is to write a function that will take a this object and return a new object that will have the type (Query -> GeneratedQueries):

type GeneratedQueries = {
  [Key in string]: <Input, Result>(input: Input) => Promise<Result>;
}

So, how do define/represent this Query type than can have any type of for its generic arguments?

CodePudding user response:

Would this suffice?

type Input='x' |'y' | 'z'
type Result ='a' |'b' | 'z'

type PreparedQuery<Input, Result> = {
  input: Input;
  result: Result;
};

type Query<I extends Input, R extends Result> = {
  [Key in string]: PreparedQuery<I, R>;
};

type GeneratedQueries = {
  [Key in string]: <Input, Result>(input: Input) => Promise<Result>;
}

CodePudding user response:

Here's one approach:

type GeneratedQueries<Q extends { [K in keyof Q]: PreparedQuery<any, any> }> =
    { [K in keyof Q]: (input: Q[K]['input']) => Promise<Q[K]['result']> }

The idea is that we allow the type parameter Q to be an object type with any property keys whatsoever, and where each property value is of type PreparedQuery<any, any>. We don't really want to constrain the input and output types to that PreparedQuery, so we use the any type as the type arguments.

Then we map over the properties of Q to produce the functions you want. For each key K in keyof Q, we index into the corresponding PreparedQuery property Q[K] to get the 'input and 'result types.

Let's test it:

interface Foo {
    a: { input: string, result: number },
    b: { input: Date, result: boolean },
    c: { input: HTMLElement, result: string }
}

type GeneratedQueriesForFoo = GeneratedQueries<Foo>
/* type GeneratedQueriesForFoo = {
    a: (input: string) => Promise<number>;
    b: (input: Date) => Promise<boolean>;
    c: (input: HTMLElement) => Promise<string>;
} */

Looks good! Note that we could also have used conditional type inference:

type GeneratedQueries<Q extends { [K in keyof Q]: PreparedQuery<any, any> }> =
    { [K in keyof Q]: Q[K] extends PreparedQuery<infer I, infer R> ?
        (input: I) => Promise<R> : never }

which gives the same result.

Playground link to code

  • Related