Typescript wrapping function with generic type

Solution 1:

The wrapper function is expected to take in a function and return a function of the exact same type signature.

Typescript has no way of knowing how many arguments the passed function takes or what it returns and your function implicitly assumes that it takes 0 arguments (except this) and returns void.

There isn't a nice way to save function signatures in typescript at the moment. However there is a new proposal for later versions that may fix this: https://github.com/Microsoft/TypeScript/issues/5453

For now you can make a generic wrapper that looks something like this.

function wrapper<T extends (...args:any[])=>any>(func: T): T {
  return <T>((...args:any[]) => {
        console.log('Wrapped Function');
        return func(...args);
    });
}

With the proposal for Variadic types this function could be written like this

function wrapper<...TArgs,TRet>(func:(...args:...TARGS)=>TRet) {
    return (...args:...TARGS) => {
        console.log("Wrapped function");
        return func(...args);
    }
}

Note the main difference here is that there is no cast the above solution had to tell the compiler that the return variable was the same type as the input variable. However with variadic types the arguments themselves can be typed generically. (Note variadic types are not currently in typescript and when the do get included it is fully possible that the above code has syntax errors)

Update for TypeScript 4.3 release: with implemented proposal working code for wrapper will look like

type InferArgs<T> = T extends (...t: [...infer Arg]) => any ? Arg : never;
type InferReturn<T> = T extends (...t: [...infer Arg]) => infer Res ? Res : never;

function getWrapper<TFunc extends (...args: any[]) => any>(func: TFunc)
    : (...args: InferArguments<TFunc>) => InferReturn<TFunc> {

    return (...args: InferArguments<TFunc>) => {
        // something before

        try {
            return func(...args);
        } finally {

            // something after;
        }
    };
}

Some short but helpful info regarding usage of Variadic Tuples with examples can be found here: https://fettblog.eu/variadic-tuple-types-preview/.

Solution 2:

Here's an alternative that preserves the argument and returns types of the inner function without depending on variadic types.

function x(message: string): void {
    console.log(`inner ${message}`);
}

export function wrapper<Args extends any[], Return>(
    operation: (...operationParameters: Args) => Return, 
    ...parameters: Args
): Return {
    console.log(`outer `);

    return operation(...parameters);
}

x("abc");
wrapper(x, "xyz");

// output:
//
// inner abc
// outer
// inner xyz

When wrapper is called with x, the TS compiler infers its type as function wrapper<[string], void>(operation: (operationParameters_0: string) => void, parameters_0: string): void.

If you try to call wrapper(x, 123), it fails with beautiful type safety: Argument of type '123' is not assignable to parameter of type 'string'.