The premise
I've got a generic API shape that looks like this:
type Macro = (...args: any[]) => Promise<any>;
type ApiObj = {
[k: string]: Macro;
}
I also have type definitions for the specific API group, like so:
type MyApi = {
getUsers(id: number): Promise<number>
getPost(title: string): Promise<string>
}
I want to extend each of these functions in the API to allow for an extra optional argument that looks like this:
type MyExtraArg = {
message: string;
}
In order to do this, I have created some utility types:
type AppendOptional<I, T extends any[]> = [...T, I?];
function isExtraArg(obj: any): obj is MyExtraArg {
return obj?.message;
}
type ExtendedFunction<T> = T extends (...args: infer U) => infer R ? (...args: AppendOptional<MyExtraArg, U>) => R : never;
type ExtendedApi<T extends ApiObj> = {
[K in keyof T]: ExtendedFunction<T[K]>
}
Then I implement my wrapping function like so:
function wrap<T extends Macro>(f: T) {
return (...args: AppendOptional<MyExtraArg, Parameters<T>>) => {
const lastArg = args.splice(-1)[0];
if(isExtraArg(lastArg)) {
console.log(lastArg.message);
}
return f(...args);
}
}
In this way, doing this (sorta) works:
const wrappedF = wrap(myApi.getUsers);
// Here I get correct intellisense with the extra optional argument
My problems
Intellisense loses the
argsnames, and transforms them intoargs_0, args_1etc.type p = Parameters<MyApi["getUsers"]> // ← this preserves the labels type p2 = AppendOptional<MyExtraArg, p> // ← this loses themAny way to make my mapped type preserve the labels?
More importantly, when I want to wrap the whole API object, I get into troubles:
function wrapApi<T extends ApiObj>(api: T) { return Object.keys(api) .reduce((acc, curr: keyof T) => { acc[curr] = wrap(api[curr]); // ← Typescript complains here, error below: * return acc; }, {} as ExtendedApi<T>); } const wrappedApi = wrapApi(myApi); // This works as above, i.e. correct intellisense, lost labels wrappedApi.getPost("a", { message: "Hello world" }); // This works, as above.
Typescript complains where the asterisk is at, error is
Type '(...args_0: Parameters<T[keyof T]>, args_1?: MyExtraArg | undefined) => Promise<any>' is not assignable to type 'ExtendedFunction<T[keyof T]>'.