tachybase_todo/packages/collections/src/database.ts
2021-09-29 07:38:05 +08:00

151 lines
3.8 KiB
TypeScript

import {
Sequelize,
ModelCtor,
Model,
Options,
SyncOptions,
Op,
Utils,
} from 'sequelize';
import { EventEmitter } from 'events';
import { Collection, CollectionOptions } from './collection';
import * as FieldTypes from './fields';
import { RelationField } from './fields';
export interface PendingOptions {
field: RelationField;
model: ModelCtor<Model>;
}
export type DatabaseOptions = Options | Sequelize;
export class Database extends EventEmitter {
sequelize: Sequelize;
fieldTypes = new Map();
models = new Map();
repositories = new Map();
operators = new Map();
collections: Map<string, Collection>;
pendingFields = new Map<string, RelationField[]>();
constructor(options: DatabaseOptions) {
super();
if (options instanceof Sequelize) {
this.sequelize = options;
} else {
this.sequelize = new Sequelize(options);
}
this.collections = new Map();
this.on('collection.afterDefine', (collection) => {
const items = this.pendingFields.get(collection.name);
for (const field of items || []) {
field.bind();
}
});
for (const [name, field] of Object.entries(FieldTypes)) {
if (['Field', 'RelationField'].includes(name)) {
continue;
}
let key = name.replace(/Field$/g, '');
key = key.substring(0, 1).toLowerCase() + key.substring(1);
this.registerFieldTypes({
[key]: field,
});
}
const operators = new Map();
// Sequelize 内置
for (const key in Op) {
operators.set('$' + key, Op[key]);
const val = Utils.underscoredIf(key, true);
operators.set('$' + val, Op[key]);
operators.set('$' + val.replace(/_/g, ''), Op[key]);
}
this.operators = operators;
}
collection(options: CollectionOptions) {
let collection = this.collections.get(options.name);
if (collection) {
collection.extend(options);
} else {
collection = new Collection(options, { database: this });
}
this.collections.set(collection.name, collection);
this.emit('collection.afterDefine', collection);
return collection;
}
getCollection(name: string) {
return this.collections.get(name);
}
addPendingField(field: RelationField) {
const associating = this.pendingFields;
const items = this.pendingFields.get(field.target) || [];
items.push(field);
associating.set(field.target, items);
}
removePendingField(field: RelationField) {
const items = this.pendingFields.get(field.target) || [];
const index = items.indexOf(field);
if (index !== -1) {
delete items[index];
this.pendingFields.set(field.target, items);
}
}
registerFieldTypes(fieldTypes: any) {
for (const [type, fieldType] of Object.entries(fieldTypes)) {
this.fieldTypes.set(type, fieldType);
}
}
registerModels(models: any) {
for (const [type, schemaType] of Object.entries(models)) {
this.models.set(type, schemaType);
}
}
registerRepositories(repositories: any) {
for (const [type, schemaType] of Object.entries(repositories)) {
this.repositories.set(type, schemaType);
}
}
registerOperators(operators) {
for (const [key, operator] of Object.entries(operators)) {
this.operators.set(key, operator);
}
}
buildField(options, context) {
const { type } = options;
const Field = this.fieldTypes.get(type);
return new Field(options, context);
}
async sync(options?: SyncOptions) {
const isMySQL = this.sequelize.getDialect() === 'mysql';
if (isMySQL) {
await this.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null);
}
const result = await this.sequelize.sync(options);
if (isMySQL) {
await this.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null);
}
return result;
}
async close() {
return this.sequelize.close();
}
}