feat: register collection sync logic (#3055)

This commit is contained in:
ChengLei Shao 2023-11-17 11:22:46 +08:00 committed by GitHub
parent cd76ae7e63
commit 76a225e354
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 73 deletions

View File

@ -20,8 +20,8 @@ describe('collection factory', function () {
static type = 'child';
}
db.collectionFactory.registerCollectionType(ChildCollection, (options) => {
return options.child == true;
db.collectionFactory.registerCollectionType(ChildCollection, {
condition: (options) => options.child,
});
const collection = db.collectionFactory.createCollection({
@ -37,4 +37,27 @@ describe('collection factory', function () {
expect(collection2).toBeInstanceOf(Collection);
});
it('should register collection type with sync logic', async () => {
class ChildCollection extends Collection {
static type = 'child';
}
const fn = jest.fn();
db.collectionFactory.registerCollectionType(ChildCollection, {
condition: (options) => options.child,
onSync(model, options) {
fn();
},
});
const collection = db.collectionFactory.createCollection({
name: 'child',
child: true,
});
await collection.sync();
expect(fn).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,28 +1,38 @@
import { Collection, CollectionOptions } from './collection';
import Database from './database';
import { Model } from './model';
type CollectionTypeOptions = {
condition: (options: CollectionOptions) => boolean;
onSync?: (model: typeof Model, options: any) => Promise<void>;
};
export class CollectionFactory {
private collectionTypes: Array<{
ctor: typeof Collection;
condition: (options: CollectionOptions) => boolean;
}> = [];
// Using a Map with the collection subclass as the key and options as the value
public collectionTypes: Map<typeof Collection, CollectionTypeOptions> = new Map();
constructor(private database: Database) {}
registerCollectionType(collectionClass: typeof Collection, condition: (options: CollectionOptions) => boolean) {
this.collectionTypes.push({ ctor: collectionClass, condition });
registerCollectionType(
collectionClass: typeof Collection, // Using the collection class as the key
options: CollectionTypeOptions,
) {
// Storing the options associated with the collection class
this.collectionTypes.set(collectionClass, options);
}
createCollection<T extends Collection>(options: CollectionOptions): T {
let klass = Collection;
for (const { ctor, condition } of this.collectionTypes) {
if (condition(options)) {
createCollection<T extends Collection>(collectionOptions: CollectionOptions): T {
let klass: typeof Collection = Collection;
// Iterating over the map to find the right class based on the condition
for (const [ctor, options] of this.collectionTypes) {
if (options.condition(collectionOptions)) {
klass = ctor;
break;
}
}
return new klass(options, {
return new klass(collectionOptions, {
database: this.database,
}) as T;
}

View File

@ -313,16 +313,22 @@ export class Database extends EventEmitter implements AsyncEmitter {
}
registerCollectionType() {
this.collectionFactory.registerCollectionType(InheritedCollection, (options) => {
this.collectionFactory.registerCollectionType(InheritedCollection, {
condition: (options) => {
return options.inherits && lodash.castArray(options.inherits).length > 0;
},
});
this.collectionFactory.registerCollectionType(ViewCollection, (options) => {
this.collectionFactory.registerCollectionType(ViewCollection, {
condition: (options) => {
return options.viewName || options.view;
},
});
this.collectionFactory.registerCollectionType(SqlCollection, (options) => {
this.collectionFactory.registerCollectionType(SqlCollection, {
condition: (options) => {
return options.sql;
},
});
}

View File

@ -31,6 +31,69 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
protected _changedWithAssociations = new Set();
protected _previousDataValuesWithAssociations = {};
static async sync(options) {
if (this.collection.isView()) {
return;
}
if (this.collection.options.sync === false) {
return;
}
// @ts-ignore
const collectionSyncOptions = this.database.collectionFactory.collectionTypes.get(this.collection.constructor)
?.onSync;
if (collectionSyncOptions) {
await collectionSyncOptions(this, options);
return;
}
const model = this as any;
const _schema = model._schema;
if (_schema && _schema != 'public') {
await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${_schema}";`, {
raw: true,
transaction: options?.transaction,
});
}
// fix sequelize sync with model that not have any column
if (Object.keys(model.tableAttributes).length === 0) {
if (this.database.inDialect('sqlite', 'mysql')) {
console.error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
return;
}
// @ts-ignore
const queryInterface = this.sequelize.queryInterface;
if (!queryInterface.patched) {
const oldDescribeTable = queryInterface.describeTable;
queryInterface.describeTable = async function (...args) {
try {
return await oldDescribeTable.call(this, ...args);
} catch (err) {
if (err.message.includes('No description found for')) {
return [];
} else {
throw err;
}
}
};
queryInterface.patched = true;
}
}
if (this.collection.isInherited()) {
return SyncRunner.syncInheritModel(model, options);
}
return SequelizeModel.sync.call(this, options);
}
// TODO
public toChangedWithAssociations() {
// @ts-ignore
@ -149,58 +212,4 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
return lodash.orderBy(data, orderItems, orderDirections);
}
static async sync(options) {
if (this.collection.isView()) {
return;
}
if (this.collection.options.sync === false) {
return;
}
const model = this as any;
const _schema = model._schema;
if (_schema && _schema != 'public') {
await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${_schema}";`, {
raw: true,
transaction: options?.transaction,
});
}
// fix sequelize sync with model that not have any column
if (Object.keys(model.tableAttributes).length === 0) {
if (this.database.inDialect('sqlite', 'mysql')) {
console.error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
return;
}
// @ts-ignore
const queryInterface = this.sequelize.queryInterface;
if (!queryInterface.patched) {
const oldDescribeTable = queryInterface.describeTable;
queryInterface.describeTable = async function (...args) {
try {
return await oldDescribeTable.call(this, ...args);
} catch (err) {
if (err.message.includes('No description found for')) {
return [];
} else {
throw err;
}
}
};
queryInterface.patched = true;
}
}
if (this.collection.isInherited()) {
return SyncRunner.syncInheritModel(model, options);
}
return SequelizeModel.sync.call(this, options);
}
}