There are two parts to this problem, the first is getting the public signature of the function right. We want the function to take in a key to some.Api and return a value of the same type as the original key. To do this we will need an extra type parameter K that extends keyof some.Api and we will use type query to tell the compiler the return is the same as the type of the passed in field (some.Api[K])
declare namespace some { type Api = { foo(n: number): string; bar(s: string, n: number) : string } function api(): Api; } function wrapCallByName<K extends keyof some.Api>(functionName: K) : some.Api[K] { return ((...args: any[]) => { try { return (some.api()[functionName] as any)(...args); } catch (error) { throw error; } }) as any; } const foo = wrapCallByName('foo') foo(1) //returns string const bar = wrapCallByName('bar') bar('', 1) //returns string
Playground
As you can see the above implementation has a lot of type assertions to any. This is because there are several problems. Firstly the index access to the api will result in an uncallable union of all fields in the API. secondly the function we return is not compatible with any field of the api. To get around this we can use an extra indirection that will make the compiler look at the values of the object as just functions with the signature (... a: any[]) =>any. This will eliminate the need for any assertions.
declare namespace some { type Api = { foo(n: number): string; bar(s: string, n: number): string } function api(): Api; } function wrapBuilder<T extends Record<keyof T, (...a: any[]) => any>>(fn: () => T) { return function <K extends keyof T>(functionName: K): T[K] { return ((...args: any[]) => { try { return fn()[functionName](...args); } catch (error) { throw error; } }); } } const wrapCallByName = wrapBuilder(() => some.api()); const foo = wrapCallByName('foo'); foo(1); //returns string const bar = wrapCallByName('bar'); bar('', 1); //returns string
Playground
I mention this nice, no assertion implementation because your question specifically denands it, personally I would be comfortable enough with the first version, especially if you api exposes only functions. The wrapper function needs to be written only once and I can't think of a situation where the assertions will cause a problem, the more important part is the for the public signature to forward the types correctly.
some.api?some.apiis actuallyfirebase.authin my use-case, so the type is(app?: firebase.app.App) => firebase.auth.Auth