0

I'm trying to define a function that will take a set of functions and wrap them in MobX's flow. My actual code does a bit more than just wrap with flow, but that isn't important to this question. I've devised some TypeScript types that will successfully do the mapping, but am having trouble getting the function to match the types.

First I thought I could use use lodash's mapValues like this:

const actions = {
    a: function* (a: number) { return "test" },
    b: function* (b: string) { return 1 }
};

const flowed = mapValues(actions, f => flow(f));

But mapValues typing assumes that every property has the same type, so this generates an error:

(parameter) f: ((a: number) => Generator<never, string, unknown>) | ((b: string) => Generator<never, number, unknown>)
Argument of type '((a: number) => Generator<never, string, unknown>) | ((b: string) => Generator<never, number, unknown>)' is not assignable to parameter of type '(a: number) => Generator<any, string, any> | AsyncGenerator<any, string, any>'.
  Type '(b: string) => Generator<never, number, unknown>' is not assignable to type '(a: number) => Generator<any, string, any> | AsyncGenerator<any, string, any>'.
    Types of parameters 'b' and 'a' are incompatible.
      Type 'number' is not assignable to type 'string'.ts(2345)

Doing some research and experimenting, I came up with the following generic types to do the conversion:

type Flowing<T> = T extends (...args: infer A) => Generator<any, infer R, any> ? (...args: A) => Promise<R> : never;

type Flowify<T> = {
    [P in keyof T]: Flowing<T[P]>;
}

declare function flowify<T>(o: T): Flowify<T>;

const flowed = flowify(actions);

Now flowed has the correct type:

{
    a: (a: number) => Promise<string>;
    b: (b: string) => Promise<number>;
}

The problem is that I haven't figured out how to create the flowify function so that it matches the types without casting. Here is what I have so far, but I'd like to avoid having to use as:

function flowify<T extends object>(o: T): Flowify<T> {
    return mapValues(o, f => flow(f as any)) as Flowify<T>;
}

I've already worked on this task for a few hours and hope to get some expert eyes on the problem. I've got this code in a TypeScript Playground.

Michael Best
  • 16,623
  • 1
  • 37
  • 70
  • I think the type assertion here is not that big a deal. Generic function implementation with conditional or mapped types tend to have type assertions. – Titian Cernicova-Dragomir May 13 '20 at 04:31
  • Just because they often do doesn't mean they have to. Are you saying that it's not possible to write a function for this with correct typing? – Michael Best May 13 '20 at 22:11

0 Answers0