I'm designing a small API. In it, developers define a class, and a configuration file that allows them to use it with my system.
Below is a simplification of my code:
abstract class Box<T> {
constructor(protected initialValue: T) {}
public abstract getDefaultThing(): T;
}
type BoxType = typeof Box;
interface Config {
boxType: BoxType;
initialValue: any;
}
function loadConfig(config: Config) {
// Need to "as any" this so TSC doesn't complain about abstract class
return new (config.boxType as any)(config.initialValue);
}
Here I define abstract class Box with generic parameter T. I also provide an interface for writing their configuration object that wants a reference to the instantiable Box class, as well as an initialValue. Finally a small implementation function that loads the config by making a new instance of the Box with the value initialValue.
A simple use of this would look like this:
class MyBox extends Box<string> {
public getDefaultThing(): any {
return `${this.initialValue} world`;
}
}
const config: Config = {
boxType: MyBox,
initialValue: "hello",
};
const loaded = loadConfig(config);
console.log(loaded.getDefaultThing()); // prints "hello world"
This already doesn't work - boxType in config has an error because MyBox is more narrow than Box. That's fine, what I'd really like to do is redefine my Config interface to type-check initialValue and BoxType:
type BoxType<T> = typeof Box<T>;
interface Config<T> {
boxType: BoxType<T>;
initialValue: T;
}
This doesn't work... at all... Box is already a type, and can't accept generics without being instantiated. I know on some level I'm mixing values and types, but is there any way to have a property be a reference to an instantiable generic class narrowed to a certain generic type?