I want to build some kind of FactoryFactory
: Basically a generic function that returns a factory function. Writing the function itself is simple, but I can't figure out how to do the TypeScript typings for it.
The function should be used like this:
const stubFactoryFunction = (...props) => (...values) => ({ /* ... */ });
const factory = stubFactoryFunction("prop1", "prop2");
const instance = factory("foo", 42);
console.log(instance); // { prop1: "foo", prop2: 42 }
At first I tried to provide the value types as an array:
type FactoryFunction<T extends any[]> =
<K extends string[]>(...props: K) =>
(...values: T[number]) =>
{[key in K[number]]: T[number]}
But this will result in { prop1: string | number, prop2: string | number}
, because the type doesn't match the array indexes.
Next I tried to provide the whole object as generic type:
type FactoryFunction<T extends {[key: string]: any}> =
(...props: (keyof T)[]) =>
(...values: ???) =>
T
And here I got a similar problem: values
must somehow match the order of props
.
Is this possible at all?
Bonus 1: Don't allow duplicate props.
Bonus 2: Enforce the provide all non-optional props from T
.
This is another solution, generic this time and finally not so complicated, eventhough some intermediary types are necessary. The base ideas are:
P
(for "Props") (extends string[]
) is done with a mapped type { [K in P[number]]: ... }
K
(one of the "Props") in P
. It's done using another mapped type IndexOf
, itself using a third mapped type Indexes
.V[IndexOf<P, K>]
which can be acceptable only if IndexOf<P, K>
is an index of V
(for "Values"), hence the conditional type IndexOf<P, K> extends keyof V ? V[IndexOf<P, K>] : never
. We will never have never
since both P
and V
array types have the same length due to the constraint V extends (any[] & { length: P['length'] })
.// Utility types
type Indexes<V extends any[]> = {
[K in Exclude<keyof V, keyof Array<any>>]: K;
};
type IndexOf<V extends any[], T> = {
[I in keyof Indexes<V>]: V[I] extends T ? T extends V[I] ? I : never : never;
}[keyof Indexes<V>];
type FactoryFunctionResult<P extends string[], V extends (any[] & { length: P['length'] })> = {
[K in P[number]]: IndexOf<P, K> extends keyof V ? V[IndexOf<P, K>] : never;
};
// Tests
type IndexesTest1 = Indexes<['a', 'b']>; // { 0: "0"; 1: "1" }
type IndexOfTest1 = IndexOf<['a', 'b'], 'b'>; // "1"
type IndexOfTest2 = IndexOf<['a', 'b'], string>; // never
type IndexOfTest3 = IndexOf<[string, string], 'a'>; // never
type IndexOfTest4 = IndexOf<[string, string], string>; // "0" | "1"
type FactoryFunctionResultTest1 = FactoryFunctionResult<['a'], [string]>; // { a: string }
type FactoryFunctionResultTest2 = FactoryFunctionResult<['a', 'b'], [string, number]>; // { a: string; b: number }
type FactoryFunctionResultTest3 = FactoryFunctionResult<['a', 'b', 'c'], [string, number, boolean]>; // { a: string; b: number; c: boolean }
type FactoryFunctionResultTest4 = FactoryFunctionResult<['a', 'b', 'c', 'd'], [string, number, boolean, string]>; // { a: string; b: number; c: boolean; d: string }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With