-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathindex.ts
83 lines (71 loc) · 2.17 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// <utils>
// by Dima Parzhitsky https://github.com/parzh (https://stackoverflow.com/a/68254847/206879)
type WithOptionalKeys<
OriginalObject extends object,
OptionalKey extends keyof OriginalObject = never
> = Omit<OriginalObject, OptionalKey> &
Partial<Pick<OriginalObject, OptionalKey>>;
type KeyOfByValue<Obj extends object, Value> = {
[Key in keyof Obj]: Obj[Key] extends Value ? Key : never;
}[keyof Obj];
type RequiredKeys<Obj extends object> = Exclude<
KeyOfByValue<Obj, Exclude<Obj[keyof Obj], undefined>>,
undefined
>;
type OptionalParamIfEmpty<Obj extends object> = RequiredKeys<Obj> extends never
? [Obj?]
: [Obj];
// </utils>
interface Options {
version: string;
customDefault?: string;
customOption?: string;
}
interface Constructor<Params extends object, Instance extends object = object> {
new (...args: any[]): Instance;
// new (...args: OptionalParamIfEmpty<Params>): Instance;
}
class Base {
static defaults<
OptionalKey extends keyof Options,
S extends Constructor<WithOptionalKeys<Options, OptionalKey>, Base>
>(this: S, defaults: { [Key in OptionalKey]: Options[Key] }): S {
return class extends this {
constructor(
...[partialParams]: OptionalParamIfEmpty<
WithOptionalKeys<Options, OptionalKey>
>
);
constructor(...args: any[]) {
super({ ...defaults, ...args[0] } as Options);
}
};
}
public options: Options;
constructor(options: Options) {
this.options = options;
}
}
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()`
customDefault: "",
});
const MyBaseWithVersion = Base.defaults({
version: "1.2.3",
customDefault: "",
});
const testWithDefaults = new MyBaseWithVersion({
// `version` should not be required to be set at all
customOption: "",
});
// should be both typed as string
testWithDefaults.options.version;
testWithDefaults.options.customDefault;
testWithDefaults.options.customOption;
const MyCascadedBase = Base.defaults({ customDefault: "1" }).defaults({
version: "1.2.3",
});