typinferenz - Typescript - omsluta funktioner samtidigt som signaturer bevaras


Jag försöker lista ut hur man omsluter definierade funktioner så att jag kan göra ytterligare arbete samtidigt som deras signaturer bevaras. Här är den önskade effekten:

Programmeraren definierar gränssnitt:

const actions = {
    first: (id: number) => {/*...*/},
    second: (name: string) => {/*...*/}
}
let actionsInterface = wrap(actions)
export actionsInterface

actionsInterface bör (dvs. det är målet) ha följande gränssnitt:

{
    first: (id: number) => void,
    second: (name: string) => void
}

Det ger i princip samma exakta gränssnitt (dvs samma lista med funktioner, med samma parametrar, utan att räkna med returtypen) som det först definierades, men det finns ytterligare arbete som utförs, som injicerades av wrap().

Min nuvarande implementering är något i stil med:

type VarFn = (...args: any) => any

function wrap<T, K extends keyof T>
(funcList: Record<K, T[K] extends VarFn ? T[K] : never>) {

    // this maps a specific function to a function that does something extra
    function wrapOne<T extends (...args: any)=>any>(fn: T) {
        return (...args: Parameters<typeof fn>) => {
            someMagicThingyExtra(fn(args))
        }
    }

    // we iterate through the list and map each function to the one that's doing something extra
    type FuncMap = Record<K, (...args: Parameters<T[K] extends VarFn ? T[K] : never>)=>void>
    let map: FuncMap
    for (var Key in funcList) {
        let func = funcList[Key]
        map[Key] = wrapOne(func)
    }
    return map
}

Jag får dock följande fel på wrap(actions):

Argument of type '{ first: (id: number) => void; second: (name: string) => void; }' is not assignable to parameter of type 'Record<"first" | "second", never>'.
  Types of property 'first' are incompatible.
    Type '(id: number) => void' is not assignable to type 'never'.

Så, av någon anledning matchade det inte (id: number) => void med (...args: any) => any, så det härledde never.

Så jag provade en lite annorlunda sak:

function wrap2<T, K extends keyof T, U extends VarFn>
(funcList: Record<K, U>) {

    function wrapOne<T extends (...args: any)=>any>(fn: T) {
        return (...args: Parameters<typeof fn>) => {
            someMagicThingyExtra(fn(args))
        }
    }

    type FuncMap = Record<K, (...args: Parameters<U>)=>void>
    let map: FuncMap
    for (var Key in funcList) {
        let func = funcList[Key]
        map[Key] = wrapOne(func)
    }
    return map
}

Inga fel, men min returtyp för wrap2(actions) är:

{
    first: (...args: any) => void
    second: (...args: any) => void
}

...och jag förlorade typer av parametrar, vilket motverkar hela syftet med att försöka kapsla in funktionaliteten, men bevara signaturerna (dvs. parametrarnas typer).

All hjälp eller vägledning är välkommen. Tack!

REDIGERA :

Dragomir gav ett svar som helt bevarar signaturen (både parametrarnas typer och returtyper). Mitt användningsfall behövde vidare ändra returtypen till void och så här uppnådde jag det:

function wrap<T extends Record<keyof T, (...args: any)=>any>>(funcList: T) {

    // this maps a specific function to a function that does something extra
    function wrapOne<T extends (...args: any) => any>(fn: T) {
        return ((...args: Parameters<typeof fn>): void => {
            someMagicThingyExtra(fn(args))
        })
    }

    // we iterate through the list and map each function to the one that's doing something extra
    type WrapMap = {
        [K in keyof T]: (...args: Parameters<T[K]>)=>void
    }
    let map: WrapMap
    for (var Key in map) {
        map[Key] = wrapOne(funcList[Key])
    }
    return map
}

Svar #1

Din generiska typ T bör ha en begränsning som säger att alla dess medlemsvärden är av typen VarFn, vilket du enkelt kan göra med T extends Record<keyof T, VarFn>. Eftersom den returnerade typen är exakt samma som indata-typen kan map helt enkelt vara av typen T.

type VarFn = (...args: any) => any

function wrap<T extends Record<keyof T, VarFn>>(funcList: T) {

  // this maps a specific function to a function that does something extra
  function wrapOne<T extends (...args: any) => any>(fn: T): T {
    return ((...args: Parameters<typeof fn>) => {
      return someMagicThingyExtra(fn(args))
    }) as T
  }

  // we iterate through the list and map each function to the one that's doing something extra
  let map = {} as T
  for (var Key in funcList) {
    let func = funcList[Key]
    map[Key] = wrapOne(func)
  }
  return map
}

const actions = {
  first: (id: number) => {/*...*/ },
  second: (name: string) => {/*...*/ }
}
let actionsInterface = wrap(actions)

Lekplatslänk

Svar #2

Detta är det första jag landade på. Det behåller till och med generics för den inslagna funktionen:

const newFunction = ((one, two) => {
    const result = oldFunction(one, two)
    // ...
    return result
}) as typeof oldFunction