Miroslav Gregor
Miroslav Gregor

Reputation: 137

Typescript - Type function that returns UNION of return values of each function passed as parameters

May I please ask if anyone is able to help me with adding types to javascript function below in typescript? In best case scenario I would like to avoid using 'any' but I was not able to figure this out even after whole day of googling.

Function in javascript:

function executeAll(...funcs) {
  const result = funcs.reduce((currentResult, nextFunc) => {
    return {
      ...currentResult,
      ...nextFunc(),
    };
  }, {});

  return result;
}

Example of usage:

const result = executeAll(
    () => { return { firstResult: "Abc" } },
    () => { return { secondResult: 123 } });
  
console.log(result);

Function in example creates object like this: { firstResult: "Abc", secondResult: 123 }

Basically the result of "executeAll" should be UNION of return values of each function passed in. Is something like that even possible or I am trying to achieve something that can not be done?

Any help would be much appreciated.

Thanks,

Miro

Upvotes: 1

Views: 55

Answers (1)

Aplet123
Aplet123

Reputation: 35560

You can do this with the Spread type outlined in this post as well as a recursive type:

// =========
// spread type
// =========

// Names of properties in T with types that include undefined
type OptionalPropertyNames<T> =
  { [K in keyof T]: undefined extends T[K] ? K : never }[keyof T];

// Common properties from L and R with undefined in R[K] replaced by type in L[K]
type SpreadProperties<L, R, K extends keyof L & keyof R> =
  { [P in K]: L[P] | Exclude<R[P], undefined> };

type Id<T> = {[K in keyof T]: T[K]} // see note at bottom*

// Type of { ...L, ...R }
type Spread<L, R> = Id<
  // Properties in L that don't exist in R
  & Pick<L, Exclude<keyof L, keyof R>>
  // Properties in R with types that exclude undefined
  & Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>
  // Properties in R, with types that include undefined, that don't exist in L
  & Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>
  // Properties in R, with types that include undefined, that exist in L
  & SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
  >;

// =========
// actual type
// =========

type Func = (...args: any) => any;

type FuncMerge<T extends Func[]> =
    T extends [infer H, ...infer R]
        ? H extends Func ? R extends Func[]
            ? Spread<ReturnType<H>, FuncMerge<R>>
            : never : never
        : {};

function executeAll<T extends Func[]>(...funcs: T): FuncMerge<T> {
  const result = funcs.reduce((currentResult, nextFunc) => {
    return {
      ...currentResult,
      ...nextFunc(),
    };
  }, {});

  return result as FuncMerge<T>;
}

const result = executeAll(
    () => { return { firstResult: "Abc" } },
    () => { return { secondResult: 123 } });

type Foo = typeof result;

Playground link

Upvotes: 1

Related Questions