deepkit-framework
deepkit-framework copied to clipboard
[type] mixin constructor hard to use + type unsafe
Currently, mixin()
creates a class where the constructor calls the constructor of all mixins. This is quite inflexible when you want to populate all required properties of these mixins.
It would be nice for the mixin()
function to have a different constructor that makes it easy to instantiate all mixins.
This would also help with type safety, as it prevent cases required fields are undefined.
Example
export class Buyable {
constructor(@t public price: number) {}
}
export class Curated {
constructor(@t public description: string, @(t.minimum(1).maximum(5)) public rating: number) {}
}
export class Book extends mixin(Buyable, Curated) {
constructor(@t public title: string, @t public author: string) {
super();
}
}
const book = new Book('Typescript for Dummies', 'John Doe');
book.price; // type is number, but is undefined!
Suggested solution
export interface ClassType<T = any, P extends unknown[] = any[]> {
new (...args: P): T;
}
export type ExtractManyClassTypeArguments<
T extends readonly ClassType[],
S extends readonly any[] = [],
> = T extends readonly []
? S
: T extends [infer H, ...infer T]
? T extends ClassType[]
? ExtractManyClassTypeArguments<T, [...S, ConstructorParameters<H>]>
: never
: never;
declare function mixin<T extends readonly ClassType[]>(
...classTypes: T
): ClassType<UnionToIntersection<ExtractClassType<T[number]>>, ExtractManyClassTypeArguments<T>>;
export class Buyable {
constructor(@t public price: number) {}
}
export class Curated {
constructor(@t public description: string, @(t.minimum(1).maximum(5)) public rating: number) {}
}
export class Book extends mixin(Buyable, Curated) {
constructor(
@t public title: string,
@t public author: string,
buyableConstructor: ConstructorParameters<typeof Buyable>,
curatedConstructor: ConstructorParameters<typeof Curated>,
) {
super(buyableConstructor, curatedConstructor);
}
}
const book = new Book(
'Typescript for Dummies',
'John Doe',
[1],
['This book teaches you everything you need to know about Typescript!', 5],
);
One problem with this solution is that it is not compatible with functional schemas, as this solution relies on constructors to determine which properties are optional when instantiating. The type for functional schemas doesn't have such information