2
type Foo = {
    (trx: Transaction | undefined, event: SomeEnum.OPT_1, params: { whatever: number }): Promise<void>;
    (trx: Transaction | undefined, event: SomeEnum.OPT_2, params: { whatever: string }): Promise<void>;
};

export const foo: Foo = async (trx: Transaction | undefined, event: SomeEnum, params: object | undefined) => {};

I'm experimenting with function overloading, the above code works but in my real world case I will have something like 40 overloads where only some of the parameters will be different, so I was wondering, is there a way to improve the code so I don't have to replicate all the invariant elements over and over? In the example, the first parameter trx will always be the same, also the return type of the function will always be the same. I tried somethig like this but typescript clearly doesn't like it:

type FooPartial<T1 extends SomeEnum, T2> = (trx: Transaction | undefined, event: T1, params: T2) => Promise<void>;

type Foo {
    FooPartial<SomeEnum.OPT_1, { whatever: number }>
    FooPartial<SomeEnum.OPT_2, { whatever: string }>
}
flagg19
  • 1,782
  • 2
  • 22
  • 27

1 Answers1

2

You need to make dynamic overloading.

In order to do it , you need:

  1. compute each overloading in the loop

type Transaction = {
    tag: 'Transaction'
}

type Maps = {
    [SomeEnum.OPT_1]: string;
    [SomeEnum.OPT_2]: number
}


type Overloading = {
    [Ev in keyof Maps]:  (trx: Transaction | undefined, event: Ev, params: Maps[Ev]) => Promise<void>
}
  1. Now we should get only values of Overloading type
type Values<T> = T[keyof T]

type Overloading = Values<{
    [Ev in keyof Maps]:  (trx: Transaction | undefined, event: Ev, params: Maps[Ev]) => Promise<void>
}>
  1. Now, when we have union of all allowed functions, we need to convert this union to overloaded function. In order to do it you just need use an intersection &.
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Overloading =UnionToIntersection<Values<{
    [Ev in keyof Maps]:  (trx: Transaction | undefined, event: Ev, params: Maps[Ev]) => Promise<void>
}>>

Full code



enum SomeEnum {
    OPT_1 = '1',
    OPT_2 = '2'
}

type Transaction = {
    tag: 'Transaction'
}

type Maps = {
    [SomeEnum.OPT_1]: string;
    [SomeEnum.OPT_2]: number
}

type Values<T> = T[keyof T]


// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Overloading =UnionToIntersection<Values<{
    [Ev in keyof Maps]:  (trx: Transaction | undefined, event: Ev, params: Maps[Ev]) => Promise<void>
}>>

export const foo: Overloading = async (trx: Transaction | undefined, event: SomeEnum, params: Values<Maps>) => { };

foo({tag:'Transaction'}, SomeEnum.OPT_1, 'string') // ok

Playground

Here, in my blog, you can find similar solution