How to call a function from another function, but keep all of the types for that specific function

I have use case, where I want to register multiple functions, and then call them from another function.

Here's the code

const combineFunctions = <T extends Record<keyof T, T[keyof T]>>(funcs: T) => {
  return (key: keyof T, params: Parameters<T[keyof T]>): ReturnType<T[keyof T]> => funcs[key](params)
}

interface AddNumbersArgumentsType {
  a: number
  b: number
}

type AddNumbersReturnType = number

const addNumbers = (params: AddNumbersArgumentsType): AddNumbersReturnType => {
  return params.a + params.b
}

interface CombineStringWithNumberArgumentsType {
  c: string
  d: number
}

type CombineStringWithNumberReturnType = string

const combineStringWithNumber = (params: CombineStringWithNumberArgumentsType): CombineStringWithNumberReturnType => {
  return params.c + params.d
}

const funcs = {
  addNumbers,
  combineStringWithNumber
}

const funkies = combineFunctions<typeof funcs>(funcs)

const a = funkies('addNumbers', [{ // Here Paramters util function forces me to send an array
  // Here it should autocomplete only correct params from AddNumbersArgumentsType
  // But it gives me an option of all arguments all functions take
}]) // => Return type is not gotten correctly, but is a combinations of all functions return types

Main problems being

  • Parameters util forces me to use an array while I just want to get the type of params
  • Arguments autocomplete for all params from all functions instead of taking argument types specific to the function
  • Same problem with return type as with arguments

You can see the code running here

Thanks in advance :)


Solution 1:

You need an extra type parameter on the inner function to capture the actual key passed in, and use that instead of keyof T to index into T. That will bake ts resolve the actual function being passed in.

Parameters returns a tuple of parameters (there could be more) but you can use tuple and rest parameters to fix this.

const combineFunctions = <T extends Record<PropertyKey, (...a: any[]) => any>>(funcs: T) => {
  return <K extends keyof T>(key: K, ...params: Parameters<T[K]>): ReturnType<T[K]> => funcs[key](...params) 
}

///.....
const funcs = {
  addNumbers,
  combineStringWithNumber
}

const funkies = combineFunctions<typeof funcs>(funcs)

const a = funkies('addNumbers', {a: 1, b: 1}) // all type cheked

Playground Link