Commit d27177d0 authored by nanahira's avatar nanahira

add mixin

parent 4b783ff2
export * from './src/def';
export * from './src/decorators';
export * from './src/mixin';
......@@ -229,10 +229,11 @@ export function SchemaClass<T>(originalClass: ClassType<T>) {
} as unknown as ClassType<T> & Schema<Partial<T>, T>;
newClass[GeneratedSym] = schema;
newClass[OriginalClassSym] = originalClass;
newClass.prototype = Object.create(originalClass.prototype);
Object.defineProperty(newClass, 'name', {
value: originalClass.name,
});
Object.setPrototypeOf(newClass, originalClass.prototype);
Object.setPrototypeOf(newClass, originalClass);
for (const field of schemaFields) {
Object.defineProperty(newClass, field, {
configurable: true,
......
import Schema from 'schemastery';
import Schema, { never, number } from 'schemastery';
import { MatrixToUnionArray, MatrixTwist } from './matrix';
export type ClassType<T> = { new (...args: any[]): T };
......@@ -48,3 +49,26 @@ export type SchemaOptionsDict<T> = { [P in keyof T]?: SchemaOptions };
export const RefSym = Symbol('SchemasteryGenRef');
export const GeneratedSym = Symbol('GeneratedSym');
export const OriginalClassSym = Symbol('OriginalClassSym');
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
x: infer R,
) => any
? R
: never;
type ArrayToUnion<T extends any[]> = T[number];
type Intersecton<T extends any[]> = UnionToIntersection<ArrayToUnion<T>>;
export type AnyClass = { new (...args: any[]): any };
type TypeOf<T> = T extends { new (...args: any[]): infer R } ? R : never;
type ClassesToTypes<A extends any[]> = A extends [infer L, ...infer R]
? [TypeOf<L>, ...ClassesToTypes<R>]
: [];
/*type ClassesToParams<A extends any[]> = A extends [infer L, ...infer R]
? [
L extends new (...params: infer P) => any ? P : never,
...ClassesToParams<R>
]
: [];
*/
export type FusionClass<A extends AnyClass[]> =
new () => //...args: MatrixToUnionArray<MatrixTwist<ClassesToParams<A>>>
Intersecton<ClassesToTypes<A>>;
import { never } from 'schemastery';
type MatrixColumn<A extends any[], P extends any[] = []> = A extends [
infer L,
...infer R
]
? [L extends [...P, infer E, ...any[]] ? E : never, ...MatrixColumn<R, P>]
: [];
type MatrixRow<A extends any[], P extends any[] = []> = A extends [
...P,
infer TargetRow,
...any[]
]
? TargetRow
: [];
type MapAny<A extends any[]> = A extends [any, ...infer R]
? [any, ...MapAny<R>]
: [];
type IsLongerOrEqual<A extends any[], B extends any[]> = A extends [
...MapAny<B>,
...any[]
]
? true
: false;
type ShouldFinish<
A extends any[],
P extends any[],
CP extends any[] = [],
> = IsLongerOrEqual<P, MatrixRow<A, CP>> extends true
? IsLongerOrEqual<CP, A> extends true
? true
: ShouldFinish<A, P, [any, ...CP]>
: false;
export type MatrixTwist<A extends any[], P extends any[] = []> = ShouldFinish<
A,
P
> extends true
? []
: [MatrixColumn<A, P>, ...MatrixTwist<A, [any, ...P]>];
export type MatrixToUnionArray<A extends any[]> = A extends [
infer L,
...infer R
]
? [L extends any[] ? L[number] : never, ...MatrixToUnionArray<R>]
: [];
import { AnyClass, FusionClass, OriginalClassSym } from './def';
import { reflector } from './metadata/reflector';
import { Metadata } from './metadata/metadata';
export const getOriginalClass = <C extends new (...args: any[]) => any>(
cls: C,
): C => {
if (cls[OriginalClassSym]) {
return cls[OriginalClassSym];
}
return cls;
};
function applyMixin(
mixedClass: AnyClass,
sourceClass: AnyClass,
// eslint-disable-next-line @typescript-eslint/ban-types
visited = new Set<Function>(),
) {
if (visited.has(sourceClass)) {
return;
}
visited.add(sourceClass);
Object.getOwnPropertyNames(sourceClass.prototype).forEach((name) => {
if (!mixedClass.prototype[name]) {
mixedClass.prototype[name] = sourceClass.prototype[name];
}
});
const schemaKeys = reflector.getArray('SchemaMetaKey', sourceClass);
for (const name of schemaKeys) {
const schemaMeta = reflector.get('SchemaMeta', sourceClass, name);
if (!schemaMeta) {
continue;
}
Metadata.set(
'SchemaMeta',
schemaMeta,
'SchemaMetaKey',
)(mixedClass.prototype, name);
const transformerMeta = reflector.get('Transformer', sourceClass, name);
if (transformerMeta) {
Metadata.set('Transformer', transformerMeta)(mixedClass.prototype, name);
}
}
const sourceParentClass = Object.getPrototypeOf(sourceClass);
if (sourceParentClass?.prototype) {
applyMixin(sourceParentClass, sourceParentClass, visited);
}
}
export function Mixin<A extends AnyClass[]>(...classes: A): FusionClass<A>;
// eslint-disable-next-line @typescript-eslint/ban-types
export function Mixin<A extends AnyClass[]>(...classes: A): Function {
const originalClasses = classes.map(getOriginalClass);
const mixedClass = class MixedClass {};
// eslint-disable-next-line @typescript-eslint/ban-types
const visited = new Set<Function>();
originalClasses.forEach((cls) => applyMixin(mixedClass, cls, visited));
return mixedClass;
}
import Schema from 'schemastery';
import { OriginalClassSym } from '../def';
// eslint-disable-next-line @typescript-eslint/ban-types
export function getStringFromNativeType(nativeType: Function) {
......
import { RegisterSchema, SchemaProperty } from '../src/decorators';
import { Mixin } from '../src/mixin';
@RegisterSchema()
export class Sleeve {
@SchemaProperty()
size: number;
getSleeveSize() {
return this.size;
}
}
@RegisterSchema()
class Dress {
@SchemaProperty({ type: Sleeve })
sleeves: Sleeve[];
@SchemaProperty()
color: string;
getColor() {
return this.color;
}
}
@RegisterSchema()
class Shirt {
@SchemaProperty()
size: string;
pockets = 3;
getSize() {
return this.size;
}
}
@RegisterSchema()
class Cloth extends Mixin(Dress, Shirt) {
constructor(data: any) {
super();
}
@SchemaProperty()
material: string;
getMaterial() {
return this.material;
}
}
describe('Schema registration', () => {
it('should be instance', () => {
const cloth = new Cloth({
color: 'red',
sleeves: [{ size: 10 }, { size: 20 }],
size: 'M',
material: 'cotton',
});
expect(cloth.getColor()).toBe('red');
expect(cloth.getSize()).toBe('M');
expect(cloth.getMaterial()).toBe('cotton');
expect(cloth.sleeves[0].getSleeveSize()).toBe(10);
expect(cloth.sleeves[1].getSleeveSize()).toBe(20);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment