diff --git a/tdrive/backend/node/src/core/platform/services/database/api.ts b/tdrive/backend/node/src/core/platform/services/database/api.ts index 105ce97d..e9a3ab96 100644 --- a/tdrive/backend/node/src/core/platform/services/database/api.ts +++ b/tdrive/backend/node/src/core/platform/services/database/api.ts @@ -11,7 +11,7 @@ export interface DatabaseServiceAPI extends TdriveServiceProvider { getConnector(): Connector; /** - * Get entities manager (TODO: Find a better name...) + * Get entities manager */ getManager(): Manager; diff --git a/tdrive/backend/node/src/core/platform/services/database/services/orm/manager.ts b/tdrive/backend/node/src/core/platform/services/database/services/orm/manager.ts index 343720e9..59e769d4 100644 --- a/tdrive/backend/node/src/core/platform/services/database/services/orm/manager.ts +++ b/tdrive/backend/node/src/core/platform/services/database/services/orm/manager.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import _ from "lodash"; import { Connector } from "./connectors"; import { getEntityDefinition, unwrapPrimarykey } from "./utils"; @@ -9,13 +8,9 @@ import { DatabaseEntitiesRemovedEvent, DatabaseEntitiesSavedEvent } from "./type import { localEventBus } from "../../../../framework/event-bus"; export default class EntityManager> { - private toInsert: EntityType[] = []; - private toUpdate: EntityType[] = []; - private toRemove: EntityType[] = []; - constructor(readonly connector: Connector) {} - public persist(entity: any): this { + public async persist(entity: any): Promise { logger.trace( `services.database.orm.entity-manager.persist - entity: ${JSON.stringify(entity)}`, ); @@ -62,18 +57,20 @@ export default class EntityManager> { } }); + entity = _.cloneDeep(entity); if (emptyPkFields.length > 0) { - this.toInsert = this.toInsert.filter(e => e !== entity); - this.toInsert.push(_.cloneDeep(entity)); + await this.connector.upsert([entity], { action: "INSERT" }); } else { - this.toUpdate = this.toUpdate.filter(e => e !== entity); - this.toUpdate.push(_.cloneDeep(entity)); + await this.connector.upsert([entity], { action: "UPDATE" }); } + localEventBus.publish("database:entities:saved", { + entities: [entity], + } as DatabaseEntitiesSavedEvent); return this; } - public remove(entity: EntityType, entityType?: EntityType): this { + public async remove(entity: EntityType, entityType?: EntityType): Promise { if (entityType) { // eslint-disable-next-line @typescript-eslint/no-explicit-any entity = _.merge(new (entityType as any)(), entity); @@ -81,45 +78,13 @@ export default class EntityManager> { if (!entity.constructor.prototype._entity || !entity.constructor.prototype._columns) { throw Error("Cannot remove this object: it is not an entity."); } - this.toRemove = this.toRemove.filter(e => e !== entity); - this.toRemove.push(_.cloneDeep(entity)); - return this; - } - - public async flush(): Promise { - this.toInsert = _.uniqWith(this.toInsert, _.isEqual); - this.toUpdate = _.uniqWith(this.toUpdate, _.isEqual); - this.toRemove = _.uniqWith(this.toRemove, _.isEqual); + await this.connector.remove([entity]); localEventBus.publish("database:entities:saved", { - entities: this.toInsert.map(e => _.cloneDeep(e)), - } as DatabaseEntitiesSavedEvent); - - localEventBus.publish("database:entities:saved", { - entities: this.toUpdate.map(e => _.cloneDeep(e)), - } as DatabaseEntitiesSavedEvent); - - localEventBus.publish("database:entities:saved", { - entities: this.toRemove.map(e => _.cloneDeep(e)), + entities: [entity], } as DatabaseEntitiesRemovedEvent); - if (this.toInsert.length > 0) { - await this.connector.upsert(this.toInsert, { action: "INSERT" }); - } - if (this.toUpdate.length > 0) { - await this.connector.upsert(this.toUpdate, { action: "UPDATE" }); - } - if (this.toRemove.length > 0) { - await this.connector.remove(this.toRemove); - } - return this; } - - public reset(): void { - this.toInsert = []; - this.toUpdate = []; - this.toRemove = []; - } } diff --git a/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts b/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts index 38854a09..25038583 100644 --- a/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts +++ b/tdrive/backend/node/src/core/platform/services/database/services/orm/repository/repository.ts @@ -135,25 +135,20 @@ export default class Repository { }> { if (previousValue === newValue) throw new Error(`Previous and new values are identical: ${JSON.stringify(previousValue)}`); - await this.manager.flush().then(manager => manager.reset()); return this.connector.atomicCompareAndSet(entity, fieldName, previousValue, newValue); } async save(entity: EntityType, _context?: ExecutionContext): Promise { - this.manager.persist(entity); - return this.manager.flush().then(manager => manager.reset()); + await this.manager.persist(entity); } async saveAll(entities: EntityType[] = [], _context?: ExecutionContext): Promise { logger.debug("services.database.repository - Saving entities"); - - entities.forEach(entity => this.manager.persist(entity)); - return this.manager.flush().then(manager => manager.reset()); + await Promise.all(entities.map(entity => this.manager.persist(entity))); } async remove(entity: EntityType, _context?: ExecutionContext): Promise { - this.manager.remove(entity); - return this.manager.flush().then(manager => manager.reset()); + await this.manager.remove(entity); } //Avoid using this except when no choice diff --git a/tdrive/backend/node/test/unit/core/services/database/services/orm/manager.test.ts b/tdrive/backend/node/test/unit/core/services/database/services/orm/manager.test.ts index 3b1cfaca..1dbfec92 100644 --- a/tdrive/backend/node/test/unit/core/services/database/services/orm/manager.test.ts +++ b/tdrive/backend/node/test/unit/core/services/database/services/orm/manager.test.ts @@ -9,50 +9,46 @@ import WorkspaceUser, { getInstance } from "../../../../../../../src/services/wo describe('EntityManager', () => { - const subj: EntityManager = new EntityManager({ } as Connector); + let connector = { upsert: () => void 0}; + const subj: EntityManager = new EntityManager(connector as unknown as Connector); + let upsert; beforeEach(async () => { + upsert = jest.spyOn((subj as any).connector, "upsert"); }); afterEach(() => { jest.clearAllMocks(); - subj.reset(); }); test ("persist should store entity to insert if all fields for pk is empty", () => { //when - subj.persist(new TestDbEntity()); + let entity = new TestDbEntity(); + subj.persist(entity); //then - expect((subj as any).toInsert.length).toEqual(1); - expect((subj as any).toUpdate.length).toEqual(0); - expect((subj as any).toRemove.length).toEqual(0); - expect((subj as any).toInsert[0].id).toBeDefined(); - expect((subj as any).toInsert[0].company_id).toBeDefined(); + expect(upsert).toBeCalledTimes(1); + expect(upsert).toBeCalledWith([entity], {"action": "INSERT"}) }); test ("persist should store entity to insert if id is set", () => { //when - subj.persist(new TestDbEntity({id: randomUUID()})); + let entity = new TestDbEntity({id: randomUUID()}); + subj.persist(entity); //then - expect((subj as any).toInsert.length).toEqual(1); - expect((subj as any).toUpdate.length).toEqual(0); - expect((subj as any).toRemove.length).toEqual(0); - expect((subj as any).toInsert[0].id).toBeDefined(); - expect((subj as any).toInsert[0].company_id).toBeDefined(); + expect(upsert).toBeCalledTimes(1); + expect(upsert).toBeCalledWith([entity], {"action": "INSERT"}) }); test ("persist should store entity to insert if company_id is set", () => { //when - subj.persist(new TestDbEntity({company_id: randomUUID()})); + let entity = new TestDbEntity({company_id: randomUUID()}); + subj.persist(entity); //then - expect((subj as any).toInsert.length).toEqual(1); - expect((subj as any).toUpdate.length).toEqual(0); - expect((subj as any).toRemove.length).toEqual(0); - expect((subj as any).toInsert[0].id).toBeDefined(); - expect((subj as any).toInsert[0].company_id).toBeDefined(); + expect(upsert).toBeCalledTimes(1); + expect(upsert).toBeCalledWith([entity], {"action": "INSERT"}) }); test ("persist should store entity to update if all pk fields are set", () => { @@ -61,10 +57,8 @@ describe('EntityManager', () => { subj.persist(entity); //then - expect((subj as any).toUpdate.length).toEqual(1); - expect((subj as any).toInsert.length).toEqual(0); - expect((subj as any).toRemove.length).toEqual(0); - expect((subj as any).toUpdate[0]).toEqual(entity) + expect(upsert).toBeCalledTimes(1); + expect(upsert).toBeCalledWith([entity], {"action": "UPDATE"}) }); test ("persist should store entity to update if all pk fields are set and column name is different from field name", () => { @@ -73,10 +67,8 @@ describe('EntityManager', () => { subj.persist(entity); //then - expect((subj as any).toUpdate.length).toEqual(1); - expect((subj as any).toInsert.length).toEqual(0); - expect((subj as any).toRemove.length).toEqual(0); - expect((subj as any).toUpdate[0]).toEqual(entity) + expect(upsert).toBeCalledTimes(1) + expect(upsert).toBeCalledWith([entity], {"action": "UPDATE"}) }); test ("persist should store entity to insert if not all pk fields are set and column name is different from field name", () => { @@ -85,11 +77,7 @@ describe('EntityManager', () => { subj.persist(entity); //then - expect((subj as any).toUpdate.length).toEqual(0); - expect((subj as any).toInsert.length).toEqual(1); - expect((subj as any).toRemove.length).toEqual(0); - expect((subj as any).toInsert[0]).toEqual(entity); - expect(entity.userId).toBeDefined(); + expect(upsert).toBeCalledWith([entity], {"action": "INSERT"}) }); });