tachybase_todo/packages/database/src/table.ts

386 lines
10 KiB
TypeScript
Raw Normal View History

2020-10-24 15:34:43 +08:00
import {
InitOptions,
ModelAttributes,
ModelOptions,
ModelIndexesOptions,
Utils,
SyncOptions,
} from 'sequelize';
import {
buildField,
FieldOptions,
Relation,
BelongsTo,
BelongsToMany,
} from './fields';
import Database from './database';
import { Model, ModelCtor } from './model';
const registeredModels = new Map<string, any>();
export function registerModel(key: string, model: any) {
registeredModels.set(key, model);
}
export function registerModels(models) {
for (const key in models) {
if (models.hasOwnProperty(key)) {
registerModel(key, models[key]);
}
}
}
// TODO: 判断如果 key 是 model 直接返回
export function getRegisteredModel(key) {
if (typeof key === 'string') {
return registeredModels.get(key);
}
return key;
}
2020-10-24 15:34:43 +08:00
export interface TableOptions extends Omit<ModelOptions<Model>, 'name'|'modelName'> {
/**
* ModelOptions name
*
* name tableNamemodelName tableName modelName
* name tables models
* tableName nametableNamemodelName
*
* TODO: name, tableName, modelNamefreezeTableNameunderscored
*/
name: string;
/**
* model
*/
model?: ModelCtor<Model> | string;
2020-10-24 15:34:43 +08:00
/**
*
*/
fields?: Array<FieldOptions>;
/**
*
*/
[key: string]: any;
}
/**
* Tabel
*/
export interface TabelContext {
database: Database;
}
/**
* Model model
*
* - reinitialize = false Model.init
* - reinitialize = true Model Model.init Model.associate
* - reinitialize = modelOnly Model Model.init
*/
export type Reinitialize = boolean | 'modelOnly';
/**
*
*
* Model
*/
export class Table {
protected database: Database;
protected options: TableOptions;
protected fields = new Map<string, any>();
protected modelAttributes: ModelAttributes;
protected modelOptions: InitOptions;
/**
*
*/
protected associations = new Map<string, Relation>();
/**
* association
*/
protected associating = new Map<string, Relation>();
/**
*
*/
protected indexes = new Map<string, any>();
protected Model: ModelCtor<Model>;
protected defaultModel: ModelCtor<Model>;
2020-10-24 15:34:43 +08:00
/**
*
*/
public isThroughTable: boolean = false;
public relationTables = new Set<string>();
constructor(options: TableOptions, context: TabelContext) {
const { database } = context;
const {
name,
fields = [],
indexes = [],
model,
2020-10-24 15:34:43 +08:00
...restOptions
} = options;
this.options = options;
this.database = database;
this.modelOptions = {
modelName: name,
tableName: name,
sequelize: database.sequelize,
...restOptions,
};
// 初始化的时候获取
this.defaultModel = getRegisteredModel(model);
2020-10-24 15:34:43 +08:00
this.modelAttributes = {};
// 在 set fields 之前 model init 的原因是因为关系字段可能需要用到 model 的相关配置
this.addIndexes(indexes, 'modelOnly');
// this.modelInit('modelOnly');
this.setFields(fields);
}
public modelInit(reinitialize: Reinitialize = false) {
if (reinitialize || !this.Model) {
this.Model = this.defaultModel || class extends Model {};
2020-10-24 15:34:43 +08:00
this.Model.database = this.database;
// 关系的建立是在 model.init 之后在配置中表字段Column和关系Relation都在 fields
// 所以需要单独提炼出 associations 字段,并在 Model.init 之后执行 Model.associate
// @ts-ignore
this.Model.associate = (models: {[key: string]: ModelCtor<Model>}) => {
for (const [key, association] of this.associating) {
const { type, target } = association.getAssociationArguments();
if (this.database.isDefined(target)) {
const TargetModel = this.database.getModel(target);
// 如果关系表在之后才定义,未设置 targetKey 时targetKey 默认值需要在 target model 初始化之后才能取到
if (association instanceof BelongsTo || association instanceof BelongsToMany) {
association.updateOptionsAfterTargetModelBeDefined();
}
this.Model[type](TargetModel, association.getAssociationOptions());
// 建立关系之后,需要删除待处理的 associating避免重复和提高效率
this.associating.delete(key);
}
}
}
}
this.Model.init(this.getModelAttributes(), this.getModelOptions());
if (reinitialize === true) {
this.associating = new Map(this.associations);
// 需要额外处理 associating 的情况
// 建立表关系需要遍历多个 Model所以在这里需要标记哪些已定义的 Model 需要建立表关系
if (this.associating.size > 0) {
this.database.associating.add(this.options.name);
} else {
this.database.associating.delete(this.options.name);
}
this.database.associate();
}
}
public getName(): string {
return this.options.name;
}
public getTableName(): string {
return this.modelOptions.tableName;
}
public getOptions(): TableOptions {
return this.options;
}
public getModel(): ModelCtor<Model> {
return this.database.getModel(this.getName());
}
public getModelAttributes(): ModelAttributes {
return this.modelAttributes;
}
public getModelOptions(): InitOptions {
const { underscored = true } = this.modelOptions;
return {
underscored,
createdAt: Utils.underscoredIf('createdAt', underscored),
updatedAt: Utils.underscoredIf('updatedAt', underscored),
indexes: Array.from(this.indexes.values()),
// freezeTableName: true,
...this.modelOptions,
};
}
/**
* table names sync
*/
public getRelatedTableNames(): Set<string> {
const names = new Set<string>();
names.add(this.options.name);
for (const association of this.associations.values()) {
const target = association.getTarget();
names.add(target);
if (association instanceof BelongsToMany) {
names.add(association.getThroughName());
}
}
return names;
}
public getAssociations() {
return this.associations;
}
public getFields() {
return this.fields;
}
public setFields(fields: Array<FieldOptions>) {
this.fields.clear();
this.associating.clear();
this.associations.clear();
for (const key in fields) {
this.addField(fields[key], false);
}
this.modelInit(true);
}
public hasField(name: string) {
return this.fields.has(name);
}
public getField(name: string) {
return this.fields.get(name);
}
/**
*
*
* @param options
* @param reinitialize
*/
public addField(options: FieldOptions, reinitialize: Reinitialize = true) {
const { name, index } = options;
const field = buildField(options, {
sourceTable: this,
database: this.database,
});
// 添加字段后 table.options 中的 fields 并不会更新,这导致 table.getOptions() 拿不到最新的字段配置
// 所以在同时更新 table.options.fields 数组
if (!this.options.fields) {
this.options.fields = [];
}
const existIndex = this.options.fields.findIndex(field => field.name === name);
if (existIndex !== -1) {
this.options.fields.splice(existIndex, 1, options);
} else {
this.options.fields.push(options);
}
2020-10-24 15:34:43 +08:00
this.fields.set(name, field);
2020-10-24 15:34:43 +08:00
if (field instanceof Relation) {
// 关系字段先放到 associating 里待处理,等相关 target model 初始化之后,再通过 associate 建立关系
this.associating.set(name, field);
this.associations.set(name, field);
} else {
if (index === true) {
this.addIndex(name, false);
} else if (typeof index === 'object') {
this.addIndex({
fields: [name],
...index,
}, false);
}
this.modelAttributes[name] = field.getAttributeOptions();
}
this.modelInit(reinitialize);
return field;
}
/**
*
*
* @param options
* @param reinitialize
*/
public addIndex(options: string | ModelIndexesOptions, reinitialize: Reinitialize = true) {
if (typeof options === 'string') {
options = {
fields: [options],
};
}
// @ts-ignore
const index = Utils.nameIndex(options, this.modelOptions.tableName);
this.indexes.set(index.name, {
type: '',
parser: null,
...index,
});
this.modelInit(reinitialize);
}
/**
*
*
* @param indexes
* @param reinitialize
*/
public addIndexes(indexes: Array<string | ModelIndexesOptions>, reinitialize: Reinitialize = true) {
for (const index in indexes) {
this.addIndex(indexes[index], false);
}
this.modelInit(reinitialize);
}
/**
* API
*
* @param options
*/
public extend(options: TableOptions) {
const { fields = [], indexes = [], ...restOptions } = options;
this.modelOptions = {
...this.modelOptions,
...restOptions as any,
};
// @ts-ignore
this.options = Utils.merge(this.options, restOptions);
for (const key in fields) {
this.addField(fields[key], false);
}
this.addIndexes(indexes, false);
this.modelInit(true);
}
/**
*
*
* @param options
*/
public async sync(options: SyncOptions = {}) {
const tables = [];
for (const name of this.getRelatedTableNames()) {
tables.push(name);
}
return this.database.sync({
...options,
tables,
});
}
}
export default Table;