Initially, I was uncertain about what to search for, so it's possible that the question has already been answered elsewhere. However, after 2 days of testing and researching, I still couldn't find a solution...
I am in the process of creating a proxy that will call a backend with a specific type. The challenge arises when synchronous methods need to be converted into asynchronous ones, in order to maintain type safety on the client side by changing function signatures (similar to "promisify") from sync to async. While this transition works smoothly for regular functions, generic types in signatures get lost and become unknown...
Even with the latest TypeScript version available (currently 4.7.3), I'm unsure if there is a way to achieve this. Perhaps a TypeScript expert out there has the magic solution?
The objective is to have
const syncIdentity = <T>(o: T) => o;
// type is <T>(o:T) => T
const asyncIdentity = async <T>(o: T) => o;
// type is <T>(o:T) => Promise<T>
type PromisifiedSyncIdentity = Promisify<typeof syncIdentity>;
// want <T>(o:T) => Promise<T>
type PromisifiedAsyncIdentity = Promisify<typeof asyncIdentity>;
// want <T>(o:T) => Promise<T>
My initial attempt was:
type Promisify<F extends (...args: any[]) => any> = (...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>;
type PromisifiedSyncIdentity = Promisify<typeof syncIdentity>;
// want <T>(o:T) => Promise<T>
// received (o:unknown) => Promise<unknown> :-/
type PromisifiedAsyncIdentity = Promisify<typeof asyncIdentity>;
// want <T>(o:T) => Promise<T>
// received (o:unknown) => Promise<unknown> :-/
The second approach maintains generics for functions that are already async (retains the original type)
type Promisify<F extends (...args: any[]) => any> = F extends (...args: any[]) => infer R
? R extends Promise<any>
? F
: (...args: Parameters<F>) => Promise<ReturnType<F>>
: never;
type PromisifiedSyncIdentity = Promisify<typeof syncIdentity>;
// want <T>(o:T) => Promise<T>
// received (o:unknown) => Promise<unknown> :-/
type PromisifiedAsyncIdentity = Promisify<typeof asyncIdentity>;
// want <T>(o:T) => Promise<T>
// received <T>(o:T) => Promise<T> (YEAH! :-D)
That concludes my efforts! If any skilled TypeScript developer has a solution to preserve generics while changing function signatures, I would greatly appreciate the insight. Otherwise, confirming that it's not feasible would also be helpful.