Extender un conjunto de funciones para aceptar argumentos adicionales en Typescript
La premisa
Tengo una forma genérica de API que se ve así:
type Macro = (...args: any[]) => Promise<any>;
type ApiObj = {
[k: string]: Macro;
}
También tengo definiciones de tipo para el grupo de API específico, así:
type MyApi = {
getUsers(id: number): Promise<number>
getPost(title: string): Promise<string>
}
Quiero extender cada una de estas funciones en la API para permitir un argumento opcional adicional que se vea así:
type MyExtraArg = {
message: string;
}
Para hacer esto, he creado algunos tipos de utilidad:
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]>
}
Luego implemento mi función de envoltura así:
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);
}
}
De esta manera, hacer esto (más o menos) funciona:
const wrappedF = wrap(myApi.getUsers);
// Here I get correct intellisense with the extra optional argument
Mis problemas
Intellisense pierde los nombres
args, y los transforma enargs_0, args_1etc.type p = Parameters<MyApi["getUsers"]> // ← this preserves the labels type p2 = AppendOptional<MyExtraArg, p> // ← this loses them¿Hay alguna manera de hacer que mi tipo mapeado preserve las etiquetas?
Más importante aún, cuando quiero envolver todo el objeto API, me meto en problemas:
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 se queja donde está el asterisco, el error es
Type '(...args_0: Parameters<T[keyof T]>, args_1?: MyExtraArg | undefined) => Promise<any>' is not assignable to type 'ExtendedFunction<T[keyof T]>'.
Enlace completo del playground de Typescript
Respuesta #1
La razón principal por la que no funcionará:
Un tipo genérico con tipo condicional que depende de una variable de tipo no puede resolverse hasta que se pase un tipo específico. La restricción a la variable de tipo no es suficiente para resolver dicho tipo genérico.
Mis consideraciones:
Creo que la restricción al tipo de variable debería ser suficiente para resolver este tipo de genéricos.
Una de las soluciones podría ser:
Área de juego de Typescript