Reputation: 5204
I'm struggling with how to type this function. I have a function that can take n
parameters of type ModelOf<T, A>
and returns type of ModelOf<T_n1 & T_n2 &T_n3..., A_n1 | A_n2 | A_n3...>
Where T_n
and A_n
are the generic parameters for the models passed into the function. Here is what I have so far.
export interface ModelOf<T, A extends string> {
initial: Record.Factory<T>;
actions: (
update$: Subject<
(state: Record<T> & Readonly<T>) => Record<T> & Readonly<T>
>
) => {
[p in A]: (...P: any) => void;
};
}
const modelA : ModelOf<ShapeOfModelA, ActionsOfModelA> = ...
const modelB : ModelOf<ShapeOfModelB, ActionsOfModelB> = ...
const combineModels = (...models : ModelOf<any, any>) : ModelOf<any, any> => {...}
const model = combineModels(modelA, modelB); // ModelOf<any, any> is not what I want
It works but I lose type safety along the way. Is there a way to accomplish this without using any
Upvotes: 1
Views: 48
Reputation: 74690
You could write the combineModels
function type like this:
declare function combineModels<T extends ModelOf<any, any>[]>(
...models: T
): T extends ModelOf<infer I, infer U>[]
? ModelOf<UnionToIntersection<I>, U>
: never;
type UnionToIntersection<T> = (T extends any
? (arg: T) => void
: never) extends (arg: infer I) => void
? I
: never;
, which makes use of generic rest parameters to infer the tuple type for the models
function parameter. All instantiations of T
and A
in ModelOf<T, A extends string>
are then conditionally inferred as union types I
and U
for the return type. We are done, after having converted I
(the union of all T
s) to an intersection type.
Test it given some models:
interface ModelOf<T, A extends string> {}
declare const modelA: ModelOf<{ a: number }, "fooAction">;
declare const modelB: ModelOf<{ b: string }, "barAction">;
const model = combineModels(modelA, modelB);
// ModelOf<{a: number} & {b: string}, "fooAction" | "barAction">
Hope, it helps!
Update: You can also write it with a lookup type instead (maybe even a bit easier):
declare function combineModels<T extends ModelOf<any, any>[]>(
...models: T
): ModelOf<UnionToIntersection<T[number]["t"]>, T[number]["a"]>
Upvotes: 2