If I create a class called Base
with a constructor that needs one object argument containing at least a version
key, the Base
class should also include a static method called .defaults()
which can set defaults for any options on the new constructor it returns.
Here is what I am trying to achieve in code:
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: Can I make the constructor's options
argument optional if none of the keys are required due to version
being set via .defaults()
?
This is the code I have written 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 forgot to mention that cascading defaults should work:
Base.defaults({ one: "" }).defaults({ two: "" })