Búsqueda de sitios web

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

  1. Intellisense pierde los nombres args, y los transforma en args_0, args_1 etc.

    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?

  2. 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