Say I have a class Base with a constructor that requires one object argument with at least a version key. The Base class also has a static .defaults() method which can set defaults for any options on the new constructor it returns.
In code, here is what I want
const test = new Base({
// `version` should be typed as required for the `Base` constructor
version: "1.2.3"
})
const MyBaseWithDefaults = Base.defaults({
// `version` should be typed as optional for `.defaults()`
foo: "bar"
})
const MyBaseWithVersion = Base.defaults({
version: "1.2.3",
foo: "bar"
})
const testWithDefaults = new MyBaseWithVersion({
// `version` should not be required to be set at all
})
// should be both typed as string
testWithDefaults.options.version
testWithDefaults.options.foo
Bonus question: is it possible to make the constructor options argument optional if none of the keys are required because version was set via .defaults()?
Here is the code I have so far:
interface Options {
version: string;
[key: string]: unknown;
}
type Constructor<T> = new (...args: any[]) => T;
class Base<TOptions extends Options = Options> {
static defaults<
TDefaults extends Options,
S extends Constructor<Base<TDefaults>>
>(
this: S,
defaults: Partial<TDefaults>
): {
new (...args: any[]): {
options: TDefaults;
};
} & S {
return class extends this {
constructor(...args: any[]) {
super(Object.assign({}, defaults, args[0] || {}));
}
};
}
constructor(options: TOptions) {
this.options = options;
};
options: TOptions;
}
Update Jul 5
I should have mentioned that cascading defaults should work: Base.defaults({ one: "" }).defaults({ two: "" })