Thanks @kaya3!
UPDATED
If you have different combination of allowed arguments, you can produce function overloads.
type MyKeys = 'lorem' | 'ipsum' | 'dolor';
interface DataParams {
lorem: { year: number };
ipsum: { to: string };
}
// credits goes to https://stackoverflow.com/a/50375286
// function intersection produces - function overloads
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type Values<T> = T[keyof T]
/**
* Generate all possible combinations of allowed arguments
*/
type AllOverloads = {
[Prop in MyKeys]:
Prop extends keyof DataParams
? (key: Prop, data: DataParams[Prop]) => any
: (key: Prop) => any
}
/**
* Convert all allowed combinations to function overload
*/
type Overloading = UnionToIntersection<Values<AllOverloads>>
const myCoolGeneric: Overloading = <Key extends MyKeys>(
key: Key,
data?: Key extends keyof DataParams ? DataParams[Key] : void
) => null as any
myCoolGeneric('lorem', { year: 2021 }); // ok
myCoolGeneric('ipsum', { to: '2021' }); // typescript error
myCoolGeneric('dolor'); // ok
Playground
In TypeScript you can use square bracket notation just like in JS:DataParams[Key]
UPDATE with generic parameters
type MyKeys = 'lorem' | 'ipsum' | 'dolor';
interface DataParams {
lorem: { year: number };
ipsum: { to: string };
}
// credits goes to https://stackoverflow.com/a/50375286
// function intersection producec - functin overloads
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type Values<T> = T[keyof T]
/**
* Generate all possible combinations of allowed arguments
*/
type AllOverloads<Keys extends string, Mappings> = {
[Prop in Keys]:
Prop extends keyof Mappings
? (key: Prop, data: Mappings[Prop]) => any
: (key: Prop) => any
}
/**
* Convert all allowed combinations to function overload
*/
type Overloading<Keys extends string, Mappings> =
UnionToIntersection<Values<AllOverloads<Keys, Mappings>>>
const myCoolGeneric: Overloading<MyKeys, DataParams> = (
key: string,
data?: unknown
) => null as any
myCoolGeneric('lorem', { year: 2021 }); // ok
myCoolGeneric('ipsum', { to: '2021' }); // typescript error
myCoolGeneric('dolor'); // ok
UPDATE 3
type MyKeys = 'lorem' | 'ipsum' | 'dolor';
interface DataParams {
lorem: { year: number };
ipsum: { to: string };
}
// credits goes to https://stackoverflow.com/a/50375286
// function intersection producec - functin overloads
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type IsNever<T> = [T] extends [UnionToIntersection<T>] ? true : false;
type Values<T> = T[keyof T]
/**
* Generate all possible combinations of allowed arguments
*/
type AllOverloads<Mappings, Keys extends string> = {
[Prop in Keys]:
Prop extends keyof Mappings
? (key: Prop, data: Mappings[Prop]) => any
: (key: Prop) => any
}
/**
* Convert all allowed combinations to function overload
*/
type Overloading<Mappings, Keys extends string> =
keyof Mappings extends Keys
? UnionToIntersection<Values<AllOverloads<Mappings, Keys>>>
: never
const myCoolGeneric: Overloading<DataParams, MyKeys> = (
key: string,
data?: unknown
) => null as any
myCoolGeneric('lorem', { year: 2021 }); // ok
myCoolGeneric('ipsum', { to: '2021' }); // typescript error
myCoolGeneric('dolor'); // ok
Try to add to DataParams
non existence key. TS will throw an error