1

What I need

I have an undetermined number of mods in an input object:

const mod1 = {
  actions: {
    a() { },
    b() { },
  }
}

const mod2 = {
  actions: {
    c() { },
    d() { },
  }
}

const input = {
  mods: {
    mod1,
    mod2
  }
}

At run time, a lib merges the mods in a single object which is equivalent to:

const output = {
  actions: {
    a() { },
    b() { },
    c() { },
    d() { },
  }
}

And I would like to create a type that would describe this single object.

What I tried

The input objects can be described like that:

interface Input {
  mods: Mods
}

interface Mods {
  [name: string]: Mod
}

interface Mod {
  actions: {
    [name: string]: () => void
  }
}

Then, I don't know how to merge the content of mods:

interface ToOutput<I extends Input> {
  actions: MergeMods<I["mods"]>
}

type MergeMods<M extends Mods> = // How to merge the content of 'M'?
Paleo
  • 21,831
  • 4
  • 65
  • 76

1 Answers1

1

Here is a solution:

type Output = ToOutput<(typeof input)["mods"]>

interface ToOutput<I extends Mods> {
  actions: UnionToIntersection<I[keyof I]["actions"]>
}

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

Explanations

The following type:

interface ToOutput<I extends Input> {
    actions: UnionToIntersection<I["mods"][keyof I["mods"]]["actions"]>
}

first uses keyof and lookups to map the Input type to match the output structure

interface ToOutputStep1<I extends Input> {
    actions: I["mods"][keyof I["mods"]]["actions"]
}

/*
type T1 = {
    actions:
      | { a: {}; b: {}; }
      | { c: {}; d: {}; };
}
*/
type T1 = ToOutputStep1<typeof input>

and then converts the actions union type to an intersection type.

/*
type T2 = {
    actions: {
        a: {};
        b: {};
        c: {};
        d: {};
    };
}
*/

type T2 = ToOutput<typeof input>

Playground

ford04
  • 66,267
  • 20
  • 199
  • 171
  • Thank you. It works. I edited your answer to put all the minimal code from the playground. – Paleo Oct 13 '19 at 14:35
  • 1
    Good hint to also include the `UnionToIntersection` type in the answer, somehow forgot that, thanks! I'll drop that `Expand` type here for illustration purposes, as it could confuse people - it's just to display the type more clearly in the IDE and doesn't change the actual type. – ford04 Oct 13 '19 at 16:54