Home > Software design >  TypeScript: How to get nested functions to union their types through a chain
TypeScript: How to get nested functions to union their types through a chain

Time:03-27

What I'm trying to do is create a series of nested functions which can be chained together, and the final callback's arguments will be a union of the parents. Example of my failed attempt so far:

TS Playground Link

export type SomeHandler<T> = (args: T) => void;

type WithFooArgs = { foo: string }
const withFoo = <T,>(handler: SomeHandler<T & WithFooArgs>) => (args: T & WithFooArgs) => handler(args);

type WithBarArgs = { bar: string }
const withBar = <T,>(handler: SomeHandler<T & WithBarArgs>) => (args: T & WithBarArgs) => handler(args);

type WithZooArgs = { zoo: string }
const withZoo = <T,>(handler: SomeHandler<T & WithZooArgs>) => (args: T & WithZooArgs) => handler(args);


export default withFoo(withBar(withZoo((args) => {
    // I want args to be type `WithFooArgs & WithBarArgs & WithZooArgs`
})));

The goal is to have a bunch of these which I can chain together in different ways.

CodePudding user response:

You are trying to change the generics of withZoo(..) based upon the expressions around it which is not possible. You could create a single generic that takes in these middleware-like callbacks then use the data from those callbacks to type a single callback function like the following:

export type SomeHandler<T> = (args: T) => void;

type WithFooArgs = { foo: string }
const withFoo = <T,>(handler: SomeHandler<T & WithFooArgs>) => (args: T & WithFooArgs) => handler(args);

type WithBarArgs = { bar: string }
const withBar = <T,>(handler: SomeHandler<T & WithBarArgs>) => (args: T & WithBarArgs) => handler(args);

type WithZooArgs = { zoo: string }
const withZoo = <T,>(handler: SomeHandler<T & WithZooArgs>) => (args: T & WithZooArgs) => handler(args);

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

function createHandler<Handler extends SomeHandler<any>>(handlers: Handler[], cb: (args: UnionToIntersection<Parameters<Parameters<Handler>[0]>[0]>) => void) {
    return (args: any) => {
        return cb(handlers.reduce((acc, cur) => cur(acc), args));
    };
}

export default createHandler([withFoo, withBar, withZoo], (args) => {
    console.log(args); // WithFooArgs & WithBarArgs & WithZooArgs
});

TypeScript Playground Link

  • Related