Is it possible to create a generic function that infers the type from the "external" object property?
Imagine something like this:
const resolversMap: {
overallRankingPlacement: (issuer: number, args: Record<string, any>, context: Record<string, any>) => Promise<number>;
} = {
overallRankingPlacement: createResolver((issuer, args, context) => {
return 0;
}),
};
function createResolver<T extends () => void>(resolverFn: T): ReturnType<T> {
return (...args) => {
// Some additional logic...
resolverFn(...args);
};
}
The goal would be so that the function returned by createResolver
would match the exact return of "overallRankingPlacement" and the arguments passed to it would match just the same—kind of mirroring/proxying everything in regards to type.
You can do this by using generic parameters for both the argument-array and the result type. Something like this will work:
function createResolver<Args extends unknown[], Res>(
resolverFn: (...args: Args) => Res
): (...args: Args) => Res {
return (...args: Args) => {
// Some additional logic...
return resolverFn(...args);
};
}
The resolversMap
example above will still fail though, as the overallRankingPlacement
function returns a promise, whereas the argument to createResolver
doesn't. You can fix it by removing Promise
from the return type or passing an asynchronous function to createResolver
to get the types in line.
Maybe what you're looking for is a createResolver
that returns an async function? If so, you could add a Promise
and an async
to the function above and get:
function createAsyncResolver<Args extends unknown[], Res>(
resolverFn: (...args: Args) => Res
): (...args: Args) => Promise<Res> {
return async (...args: Args) => {
// Some additional async logic...
return resolverFn(...args);
};
}
This version will work correctly with your sample resolversMap
.
It may be worth noting that wrapping functions like this does not handle overloaded functions well. Only the last overload is retained, so for
function overloaded(x: number): number
function overloaded(x: string): string
function overloaded(x: number | string) {
return x
}
the wrapped function will only be from string
to string
:
const unfortunatelyNotOverloaded = createResolver(overloaded)
const ok = unfortunatelyNotOverloaded('ok') // inferred type: string
const fail = unfortunatelyNotOverloaded(1)
// ERROR: Argument of type 'number' is not assignable to parameter of type 'string'.
I don't think there's any way around this yet, and maybe it won't even get supported at all.