108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
|
import ModelComponent from "./ModelComponent";
|
||
|
import Model from "./Model";
|
||
|
import ModelQuery, {ModelQueryResult} from "./ModelQuery";
|
||
|
import {Request} from "express";
|
||
|
import {Type} from "../Utils";
|
||
|
|
||
|
export default class ModelFactory<T extends Model> {
|
||
|
private static readonly factories: { [modelType: string]: ModelFactory<any> } = {};
|
||
|
|
||
|
public static register<M extends Model>(modelType: Type<M>) {
|
||
|
if (this.factories[modelType.name]) throw new Error(`Factory for type ${modelType.name} already defined.`);
|
||
|
this.factories[modelType.name] = new ModelFactory<M>(modelType);
|
||
|
}
|
||
|
|
||
|
public static get<M extends Model>(modelType: Type<M>): ModelFactory<M> {
|
||
|
const factory = this.factories[modelType.name];
|
||
|
if (!factory) throw new Error(`No factory registered for ${modelType.name}.`);
|
||
|
return factory;
|
||
|
}
|
||
|
|
||
|
private readonly modelType: Type<T>;
|
||
|
private readonly components: ModelComponentFactory<T>[] = [];
|
||
|
|
||
|
protected constructor(modelType: Type<T>) {
|
||
|
this.modelType = modelType;
|
||
|
}
|
||
|
|
||
|
|
||
|
public addComponent(modelComponentFactory: ModelComponentFactory<T>) {
|
||
|
this.components.push(modelComponentFactory);
|
||
|
}
|
||
|
|
||
|
public make(data: any): T {
|
||
|
const model = new this.modelType(data);
|
||
|
for (const component of this.components) {
|
||
|
model.addComponent(new component(model));
|
||
|
}
|
||
|
model.setFactory(this);
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
public get table(): string {
|
||
|
return this.constructor.name
|
||
|
.replace(/(?:^|\.?)([A-Z])/g, (x, y) => '_' + y.toLowerCase())
|
||
|
.replace(/^_/, '')
|
||
|
+ 's';
|
||
|
}
|
||
|
|
||
|
public select(...fields: string[]): ModelQuery<T> {
|
||
|
return ModelQuery.select(this, ...fields);
|
||
|
}
|
||
|
|
||
|
public update(data: { [key: string]: any }): ModelQuery<T> {
|
||
|
return ModelQuery.update(this, data);
|
||
|
}
|
||
|
|
||
|
public delete(): ModelQuery<T> {
|
||
|
return ModelQuery.delete(this);
|
||
|
}
|
||
|
|
||
|
public getPrimaryKeyFields(): string[] {
|
||
|
return ['id'];
|
||
|
}
|
||
|
|
||
|
public getPrimaryKey(modelData: any): any[] {
|
||
|
return this.getPrimaryKeyFields().map(f => modelData[f]);
|
||
|
}
|
||
|
|
||
|
public getPrimaryKeyString(modelData: any): string {
|
||
|
return this.getPrimaryKey(modelData).join(',');
|
||
|
}
|
||
|
|
||
|
public async getById(...id: any): Promise<T | null> {
|
||
|
let query = this.select();
|
||
|
const primaryKeyFields = this.getPrimaryKeyFields();
|
||
|
for (let i = 0; i < primaryKeyFields.length; i++) {
|
||
|
query = query.where(primaryKeyFields[i], id[i]);
|
||
|
}
|
||
|
return query.first();
|
||
|
}
|
||
|
|
||
|
public async paginate(request: Request, perPage: number = 20, query?: ModelQuery<T>): Promise<ModelQueryResult<T>> {
|
||
|
let page = request.params.page ? parseInt(request.params.page) : 1;
|
||
|
if (!query) query = this.select();
|
||
|
if (request.params.sortBy) {
|
||
|
const dir = request.params.sortDirection;
|
||
|
query = query.sortBy(request.params.sortBy, dir === 'ASC' || dir === 'DESC' ? dir : undefined);
|
||
|
} else {
|
||
|
query = query.sortBy('id');
|
||
|
}
|
||
|
return await query.paginate(page, perPage);
|
||
|
}
|
||
|
|
||
|
public async loadRelation(models: T[], relation: string, model: Function, localField: string) {
|
||
|
const loadMap: { [p: number]: (model: T) => void } = {};
|
||
|
const ids = models.map(m => {
|
||
|
m.relations[relation] = null;
|
||
|
if (m[localField]) loadMap[m[localField]] = v => m.relations[relation] = v;
|
||
|
return m[localField];
|
||
|
}).filter(id => id);
|
||
|
for (const v of await (<any>model).models((<any>model).select().whereIn('id', ids))) {
|
||
|
loadMap[v.id!](v);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export type ModelComponentFactory<T extends Model> = new (model: T) => ModelComponent<T>;
|