feat: register collection sync logic (#3055)
This commit is contained in:
parent
cd76ae7e63
commit
76a225e354
@ -20,8 +20,8 @@ describe('collection factory', function () {
|
|||||||
static type = 'child';
|
static type = 'child';
|
||||||
}
|
}
|
||||||
|
|
||||||
db.collectionFactory.registerCollectionType(ChildCollection, (options) => {
|
db.collectionFactory.registerCollectionType(ChildCollection, {
|
||||||
return options.child == true;
|
condition: (options) => options.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
const collection = db.collectionFactory.createCollection({
|
const collection = db.collectionFactory.createCollection({
|
||||||
@ -37,4 +37,27 @@ describe('collection factory', function () {
|
|||||||
|
|
||||||
expect(collection2).toBeInstanceOf(Collection);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,28 +1,38 @@
|
|||||||
import { Collection, CollectionOptions } from './collection';
|
import { Collection, CollectionOptions } from './collection';
|
||||||
import Database from './database';
|
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 {
|
export class CollectionFactory {
|
||||||
private collectionTypes: Array<{
|
// Using a Map with the collection subclass as the key and options as the value
|
||||||
ctor: typeof Collection;
|
public collectionTypes: Map<typeof Collection, CollectionTypeOptions> = new Map();
|
||||||
condition: (options: CollectionOptions) => boolean;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
constructor(private database: Database) {}
|
constructor(private database: Database) {}
|
||||||
|
|
||||||
registerCollectionType(collectionClass: typeof Collection, condition: (options: CollectionOptions) => boolean) {
|
registerCollectionType(
|
||||||
this.collectionTypes.push({ ctor: collectionClass, condition });
|
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 {
|
createCollection<T extends Collection>(collectionOptions: CollectionOptions): T {
|
||||||
let klass = Collection;
|
let klass: typeof Collection = Collection;
|
||||||
for (const { ctor, condition } of this.collectionTypes) {
|
|
||||||
if (condition(options)) {
|
// 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;
|
klass = ctor;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new klass(options, {
|
return new klass(collectionOptions, {
|
||||||
database: this.database,
|
database: this.database,
|
||||||
}) as T;
|
}) as T;
|
||||||
}
|
}
|
||||||
|
@ -313,16 +313,22 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerCollectionType() {
|
registerCollectionType() {
|
||||||
this.collectionFactory.registerCollectionType(InheritedCollection, (options) => {
|
this.collectionFactory.registerCollectionType(InheritedCollection, {
|
||||||
return options.inherits && lodash.castArray(options.inherits).length > 0;
|
condition: (options) => {
|
||||||
|
return options.inherits && lodash.castArray(options.inherits).length > 0;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.collectionFactory.registerCollectionType(ViewCollection, (options) => {
|
this.collectionFactory.registerCollectionType(ViewCollection, {
|
||||||
return options.viewName || options.view;
|
condition: (options) => {
|
||||||
|
return options.viewName || options.view;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.collectionFactory.registerCollectionType(SqlCollection, (options) => {
|
this.collectionFactory.registerCollectionType(SqlCollection, {
|
||||||
return options.sql;
|
condition: (options) => {
|
||||||
|
return options.sql;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,69 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|||||||
protected _changedWithAssociations = new Set();
|
protected _changedWithAssociations = new Set();
|
||||||
protected _previousDataValuesWithAssociations = {};
|
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
|
// TODO
|
||||||
public toChangedWithAssociations() {
|
public toChangedWithAssociations() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -149,58 +212,4 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|||||||
|
|
||||||
return lodash.orderBy(data, orderItems, orderDirections);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user