import { ColumnInfo } from './column'; import { assignWithArrays, pick as _pick } from './utils'; import { Hooks } from './hooks'; import { RelationshipInfo } from './relationships'; export const modelInfoKey = Symbol('model info'); export interface ModelCtor { new (): T; } export interface IndexInfo { name: string; keys: string[]; options?: { multi?: boolean; geo?: boolean; }; } export interface ModelInfo { database: string; table: string; columns: ColumnInfo[]; indexes: IndexInfo[]; primaryKey: string; tags: Map>; relationships: RelationshipInfo[]; } export function createModelInfo( target: ModelCtor, ...objs: Partial[] ): ModelInfo { return ((target as any)[modelInfoKey] = assignWithArrays( >{ columns: [], indexes: [], relationships: [], primaryKey: 'id', tags: new Map>(), }, (target as any)[modelInfoKey], ...objs, )); } /** * Model decorator */ export function Model(info: Partial): ClassDecorator { return (target: any) => { const modelInfo = createModelInfo(target, info); target.prototype.toJSON = function() { return [...modelInfo.columns, ...modelInfo.relationships] .filter(column => this[column.key] !== undefined) .reduce( (t, column) => ({ ...t, [column.key]: this[column.key] }), {}, ); }; }; } function getKeys( ctor: ModelCtor, tagsOrKeys: string[] | string, ): string[] { const modelTags = Model.getInfo(ctor).tags; let keys: string[]; if (Array.isArray(tagsOrKeys)) { keys = tagsOrKeys; } else { keys = [tagsOrKeys]; } return keys.reduce((keys, tagOrKey) => { if (modelTags.has(tagOrKey)) { return [...keys, ...modelTags.get(tagOrKey)!]; } return [...keys, tagOrKey]; }, []); } export namespace Model { export function construct(ctor: ModelCtor, data: Partial): M { return Model.assign(new ctor(), data); } export function assign(model: M, ...sources: Partial[]): M { return Object.assign(model, ...sources); } export function pickAssign( model: M, tagsOrKeys: string[] | string, ...sources: Partial[] ): M { const ctor = >model.constructor; const keys = getKeys(ctor, tagsOrKeys) as Array; return Model.assign( model, ...sources.map(source => _pick(source, ...keys)), ); } export function pick(model: M, ...tagsOrKeys: string[]): Partial { const ctor = >model.constructor; const modelTags = Model.getInfo(ctor).tags; const data = tagsOrKeys.reduce((target, tagOrKey) => { if (modelTags.has(tagOrKey)) { return { ...target, ..._pick(model, ...modelTags.get(tagOrKey)!), }; } return { ...target, [tagOrKey]: (model as any)[tagOrKey] }; }, {}); return Model.construct(ctor, data); } export function without(model: M, ...tagsOrKeys: string[]): Partial { const ctor = >model.constructor; const keys = getKeys(ctor, tagsOrKeys); const pickKeys = Object.keys(model).filter(key => !keys.includes(key)); return Model.construct(ctor, _pick(model, ...pickKeys)); } export function getInfo(ctor: ModelCtor): ModelInfo { return (ctor as any)[modelInfoKey]; } export function notify(model: any, hook: Hooks, ...args: any[]): any { if (model[hook]) { return model[hook](...args); } } }