What you require is to specify "take any constructor type and convert it to a function type that accepts the same arguments".
An issue arises because TypeScript's type system lacks a proper way to define "the arguments of something callable or constructible" that works for generic functions. For instance, if you have a function like:
function foo<T>(x: T, y: T): void { }
and attempt to extract its parameter list, the generic parameter T
will be replaced with its constraint. In this case, T
is unfettered, and thus has an implicit constraint of unknown
:
type FooParams = Parameters<typeof foo>;
// type FooParams = [x: unknown, y: unknown]
TypeScript does not possess appropriate generic types to represent the parameter list of a generic function. A generic function has its generic type parameters on the call signature. However, a tuple type like [x: unknown, y: unknown]
lacks a call signature and cannot hold a generic type parameter:
// Invalid in TS, do not utilize this:
type FooParams = <T>[x: T, y: T];
To address this, TypeScript would need something akin to arbitrary generic value types, as requested in microsoft/TypeScript#17574... but such a feature is absent.
Instead of focusing on a tuple type, perhaps we could automatically convert one generic function type into another. Unfortunately, once more, the language lacks suitable type operators for this task. To capture the relationship between a generic function and its type parameter, TypeScript would likely need something like "higher kinded types", as requested in microsoft/TypeScript#1213... but these are also nonexistent.
Prior to TypeScript 3.4, there may have been no solution. Nevertheless, Typescript 3.4 introduced support for higher order type inference from generic functions. While not a full implementation of higher kinded types, it enables transforming an actual generic function value into another generic function value, where the output function's type links to the input function's type precisely as needed. Although there is no available type-level syntax for using this feature, one can derive the desired type by inferring from a pseudo-function at the value level. An example for a class MyClass
is demonstrated below:
// taking advantage of TS3.4 support for higher order inference from generic functions
const ctorArgs = <A extends any[], R>(f: new (...a: A) => R): (...a: A) => void => null!
const myFunc = ctorArgs(MyClass)
type MyFunctionType = typeof myFunc;
const myFunction: MyFunctionType = (...args) => {}
myFunction("click", ev => { ev.buttons })
Although ctorArgs
accomplishes the intended type manipulation at the value level, it involves including unnecessary JavaScript code in the process. If the goal is solely focused on typing, this approach introduces unneeded complexity.
Considering the unavoidable inclusion of JavaScript code, one might leverage it to implement myFunction
generically. The end goal being to take a constructor and transform it into a void-returning function - potentially discarding its result. This concept is exemplified here:
const makeMyFunction = <A extends any[], R>(
f: new (...a: A) => R): (...a: A) => void =>
(...a) => { new f(...a) } // whatever the implementation should do
const myFunction = makeMyFunction(MyClass);
In this scenario, myFunction
is obtained effortlessly. Nonetheless, its suitability hinges on the specific use case which remains undisclosed.
Link to Playground for Code