From daa08a538cdd98e8357044cc5d610befb4efe24f Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Wed, 18 Dec 2024 19:09:58 +0100 Subject: [PATCH 1/9] fix: base repository typing --- api/src/utils/generics/base-repository.ts | 120 ++++++++++-------- .../utils/generics/lifecycle-hook-manager.ts | 7 +- .../utils/pagination/pagination-query.dto.ts | 6 +- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index c286dae0..9a35d1a7 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -80,13 +80,13 @@ export abstract class BaseRepository< private readonly emitter: EventEmitter2, readonly model: Model, private readonly cls: new () => T, - protected readonly populate: P[] = [], - protected readonly clsPopulate: new () => TFull = undefined, + protected readonly populate: P[] | undefined = undefined, + protected readonly clsPopulate: (new () => TFull) | undefined = undefined, ) { this.registerLifeCycleHooks(); } - getPopulate(): P[] { + getPopulate(): P[] | undefined { return this.populate; } @@ -98,8 +98,15 @@ export abstract class BaseRepository< private registerLifeCycleHooks(): void { const repository = this; const hooks = LifecycleHookManager.getHooks(this.cls.name); + if (!hooks) { + // eslint-disable-next-line no-console + console.warn( + `LifeCycleHooks has not been registered for ${this.cls.name}`, + ); + return; + } - hooks?.validate.pre.execute(async function () { + hooks.validate.pre.execute(async function () { const doc = this as HydratedDocument; await repository.preCreateValidate(doc); repository.emitter.emit( @@ -108,7 +115,7 @@ export abstract class BaseRepository< ); }); - hooks?.validate.post.execute(async function (created: HydratedDocument) { + hooks.validate.post.execute(async function (created: HydratedDocument) { await repository.postCreateValidate(created); repository.emitter.emit( repository.getEventName(EHook.postCreateValidate), @@ -116,13 +123,13 @@ export abstract class BaseRepository< ); }); - hooks?.save.pre.execute(async function () { + hooks.save.pre.execute(async function () { const doc = this as HydratedDocument; await repository.preCreate(doc); repository.emitter.emit(repository.getEventName(EHook.preCreate), doc); }); - hooks?.save.post.execute(async function (created: HydratedDocument) { + hooks.save.post.execute(async function (created: HydratedDocument) { await repository.postCreate(created); repository.emitter.emit( repository.getEventName(EHook.postCreate), @@ -130,7 +137,7 @@ export abstract class BaseRepository< ); }); - hooks?.deleteOne.pre.execute(async function () { + hooks.deleteOne.pre.execute(async function () { const query = this as Query; const criteria = query.getQuery(); await repository.preDelete(query, criteria); @@ -141,7 +148,7 @@ export abstract class BaseRepository< ); }); - hooks?.deleteOne.post.execute(async function (result: DeleteResult) { + hooks.deleteOne.post.execute(async function (result: DeleteResult) { const query = this as Query; await repository.postDelete(query, result); repository.emitter.emit( @@ -151,7 +158,7 @@ export abstract class BaseRepository< ); }); - hooks?.deleteMany.pre.execute(async function () { + hooks.deleteMany.pre.execute(async function () { const query = this as Query; const criteria = query.getQuery(); await repository.preDelete(query, criteria); @@ -166,30 +173,32 @@ export abstract class BaseRepository< await repository.postDelete(query, result); }); - hooks?.findOneAndUpdate.pre.execute(async function () { + hooks.findOneAndUpdate.pre.execute(async function () { const query = this as Query; const criteria = query.getFilter(); const updates = query.getUpdate(); - - await repository.preUpdate(query, criteria, updates); - repository.emitter.emit( - repository.getEventName(EHook.preUpdate), - criteria, - updates?.['$set'], - ); + if (updates) { + await repository.preUpdate(query, criteria, updates); + repository.emitter.emit( + repository.getEventName(EHook.preUpdate), + criteria, + updates?.['$set'], + ); + } }); - hooks?.updateMany.pre.execute(async function () { + hooks.updateMany.pre.execute(async function () { const query = this as Query; const criteria = query.getFilter(); const updates = query.getUpdate(); - - await repository.preUpdateMany(query, criteria, updates); - repository.emitter.emit( - repository.getEventName(EHook.preUpdateMany), - criteria, - updates?.['$set'], - ); + if (updates) { + await repository.preUpdateMany(query, criteria, updates); + repository.emitter.emit( + repository.getEventName(EHook.preUpdateMany), + criteria, + updates?.['$set'], + ); + } }); hooks?.updateMany.post.execute(async function (updated: any) { @@ -201,7 +210,7 @@ export abstract class BaseRepository< ); }); - hooks?.findOneAndUpdate.post.execute(async function ( + hooks.findOneAndUpdate.post.execute(async function ( updated: HydratedDocument, ) { if (updated) { @@ -227,18 +236,23 @@ export abstract class BaseRepository< } protected async executeOne>( - query: Query, + query: Query, cls: new () => R, options?: ClassTransformOptions, ): Promise { const doc = await query.lean(this.leanOpts).exec(); + if (!doc) { + throw new Error( + `executeOne() query did not return document to transform into ${cls.name}`, + ); + } return plainToClass(cls, doc, options ?? this.transformOpts); } protected findOneQuery( criteria: string | TFilterQuery, projection?: ProjectionType, - ): Query { + ): Query { if (!criteria) { // An empty criteria would return the first document that it finds throw new Error('findOneQuery() should not have an empty criteria'); @@ -269,9 +283,9 @@ export abstract class BaseRepository< ): Promise { this.ensureCanPopulate(); const query = this.findOneQuery(criteria, projection).populate( - this.populate, + this.populate as P[], ); - return await this.executeOne(query, this.clsPopulate); + return await this.executeOne(query, this.clsPopulate as new () => TFull); } protected findQuery( @@ -369,15 +383,15 @@ export abstract class BaseRepository< this.ensureCanPopulate(); if (Array.isArray(pageQuery)) { const query = this.findQuery(filters, pageQuery, projection).populate( - this.populate, + this.populate as P[], ); - return await this.execute(query, this.clsPopulate); + return await this.execute(query, this.clsPopulate as new () => TFull); } const query = this.findQuery(filters, pageQuery, projection).populate( - this.populate, + this.populate as P[], ); - return await this.execute(query, this.clsPopulate); + return await this.execute(query, this.clsPopulate as new () => TFull); } protected findAllQuery( @@ -392,8 +406,8 @@ export abstract class BaseRepository< async findAllAndPopulate(sort?: QuerySortDto): Promise { this.ensureCanPopulate(); - const query = this.findAllQuery(sort).populate(this.populate); - return await this.execute(query, this.clsPopulate); + const query = this.findAllQuery(sort).populate(this.populate as P[]); + return await this.execute(query, this.clsPopulate as new () => TFull); } /** @@ -429,9 +443,9 @@ export abstract class BaseRepository< ): Promise { this.ensureCanPopulate(); const query = this.findPageQuery(filters, pageQuery).populate( - this.populate, + this.populate as P[], ); - return await this.execute(query, this.clsPopulate); + return await this.execute(query, this.clsPopulate as new () => TFull); } async countAll(): Promise { @@ -475,23 +489,25 @@ export abstract class BaseRepository< new: true, }, ); + const filterCriteria = query.getFilter(); const queryUpdates = query.getUpdate(); - await this.preUpdateValidate(filterCriteria, queryUpdates); - this.emitter.emit( - this.getEventName(EHook.preUpdateValidate), - filterCriteria, - queryUpdates, - ); - - await this.postUpdateValidate(filterCriteria, queryUpdates); - this.emitter.emit( - this.getEventName(EHook.postUpdateValidate), - filterCriteria, - queryUpdates, - ); + if (queryUpdates) { + await this.preUpdateValidate(filterCriteria, queryUpdates); + this.emitter.emit( + this.getEventName(EHook.preUpdateValidate), + filterCriteria, + queryUpdates, + ); + await this.postUpdateValidate(filterCriteria, queryUpdates); + this.emitter.emit( + this.getEventName(EHook.postUpdateValidate), + filterCriteria, + queryUpdates, + ); + } return await this.executeOne(query, this.cls); } diff --git a/api/src/utils/generics/lifecycle-hook-manager.ts b/api/src/utils/generics/lifecycle-hook-manager.ts index 983b2c02..24703f4e 100644 --- a/api/src/utils/generics/lifecycle-hook-manager.ts +++ b/api/src/utils/generics/lifecycle-hook-manager.ts @@ -25,7 +25,7 @@ type PostHook = (...args: any[]) => void; interface LifecycleHook { pre: PreHook & { execute: (newCallback: PreHook) => void }; - post?: PostHook & { execute: (newCallback: PostHook) => void }; + post: PostHook & { execute: (newCallback: PostHook) => void }; } type LifecycleHooks = { @@ -77,12 +77,9 @@ export class LifecycleHookManager { for (const [op, types] of Object.entries(operations)) { const hooks: LifecycleHook = { pre: this.createLifecycleCallback() as LifecycleHook['pre'], + post: this.createLifecycleCallback() as LifecycleHook['post'], }; - if (types.includes('post')) { - hooks.post = this.createLifecycleCallback() as LifecycleHook['post']; - } - types.forEach((type) => { schema[type](op, hooks[type]); }); diff --git a/api/src/utils/pagination/pagination-query.dto.ts b/api/src/utils/pagination/pagination-query.dto.ts index 31ac2ecf..80401ab2 100644 --- a/api/src/utils/pagination/pagination-query.dto.ts +++ b/api/src/utils/pagination/pagination-query.dto.ts @@ -16,7 +16,7 @@ export type QuerySortDto = [ ]; export type PageQueryDto = { - skip: number | undefined; - limit: number | undefined; - sort: QuerySortDto; + skip: number; + limit: number; + sort?: QuerySortDto; }; From fa282fb77bf594d60c0d7b38bb5e90cb040d0629 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Thu, 19 Dec 2024 11:22:20 +0100 Subject: [PATCH 2/9] fix: base-repository typing --- api/src/utils/generics/base-repository.ts | 40 ++++++++++------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 9a35d1a7..603ccfc9 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -80,13 +80,13 @@ export abstract class BaseRepository< private readonly emitter: EventEmitter2, readonly model: Model, private readonly cls: new () => T, - protected readonly populate: P[] | undefined = undefined, - protected readonly clsPopulate: (new () => TFull) | undefined = undefined, + protected readonly populate: P[] = [], + protected readonly clsPopulate?: new () => TFull, ) { this.registerLifeCycleHooks(); } - getPopulate(): P[] | undefined { + getPopulate(): P[] { return this.populate; } @@ -148,7 +148,7 @@ export abstract class BaseRepository< ); }); - hooks.deleteOne.post.execute(async function (result: DeleteResult) { + hooks?.deleteOne.post.execute(async function (result: DeleteResult) { const query = this as Query; await repository.postDelete(query, result); repository.emitter.emit( @@ -164,7 +164,7 @@ export abstract class BaseRepository< await repository.preDelete(query, criteria); }); - hooks?.deleteMany.post.execute(async function (result: DeleteResult) { + hooks.deleteMany.post.execute(async function (result: DeleteResult) { repository.emitter.emit( repository.getEventName(EHook.postDelete), result, @@ -201,7 +201,7 @@ export abstract class BaseRepository< } }); - hooks?.updateMany.post.execute(async function (updated: any) { + hooks.updateMany.post.execute(async function (updated: any) { const query = this as Query; await repository.postUpdateMany(query, updated); repository.emitter.emit( @@ -241,11 +241,6 @@ export abstract class BaseRepository< options?: ClassTransformOptions, ): Promise { const doc = await query.lean(this.leanOpts).exec(); - if (!doc) { - throw new Error( - `executeOne() query did not return document to transform into ${cls.name}`, - ); - } return plainToClass(cls, doc, options ?? this.transformOpts); } @@ -283,9 +278,9 @@ export abstract class BaseRepository< ): Promise { this.ensureCanPopulate(); const query = this.findOneQuery(criteria, projection).populate( - this.populate as P[], + this.populate, ); - return await this.executeOne(query, this.clsPopulate as new () => TFull); + return await this.executeOne(query, this.clsPopulate!); } protected findQuery( @@ -383,15 +378,15 @@ export abstract class BaseRepository< this.ensureCanPopulate(); if (Array.isArray(pageQuery)) { const query = this.findQuery(filters, pageQuery, projection).populate( - this.populate as P[], + this.populate, ); - return await this.execute(query, this.clsPopulate as new () => TFull); + return await this.execute(query, this.clsPopulate!); } const query = this.findQuery(filters, pageQuery, projection).populate( - this.populate as P[], + this.populate, ); - return await this.execute(query, this.clsPopulate as new () => TFull); + return await this.execute(query, this.clsPopulate!); } protected findAllQuery( @@ -406,8 +401,8 @@ export abstract class BaseRepository< async findAllAndPopulate(sort?: QuerySortDto): Promise { this.ensureCanPopulate(); - const query = this.findAllQuery(sort).populate(this.populate as P[]); - return await this.execute(query, this.clsPopulate as new () => TFull); + const query = this.findAllQuery(sort).populate(this.populate); + return await this.execute(query, this.clsPopulate!); } /** @@ -443,9 +438,9 @@ export abstract class BaseRepository< ): Promise { this.ensureCanPopulate(); const query = this.findPageQuery(filters, pageQuery).populate( - this.populate as P[], + this.populate, ); - return await this.execute(query, this.clsPopulate as new () => TFull); + return await this.execute(query, this.clsPopulate!); } async countAll(): Promise { @@ -489,10 +484,8 @@ export abstract class BaseRepository< new: true, }, ); - const filterCriteria = query.getFilter(); const queryUpdates = query.getUpdate(); - if (queryUpdates) { await this.preUpdateValidate(filterCriteria, queryUpdates); this.emitter.emit( @@ -508,6 +501,7 @@ export abstract class BaseRepository< queryUpdates, ); } + return await this.executeOne(query, this.cls); } From 4972011dfd14592cb94dace6f717f2fe50e7feda Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Fri, 20 Dec 2024 08:42:02 +0100 Subject: [PATCH 3/9] fix: throw when no updates has been provided --- api/src/utils/generics/base-repository.ts | 30 ++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 603ccfc9..b8e127f7 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -177,28 +177,30 @@ export abstract class BaseRepository< const query = this as Query; const criteria = query.getFilter(); const updates = query.getUpdate(); - if (updates) { - await repository.preUpdate(query, criteria, updates); - repository.emitter.emit( - repository.getEventName(EHook.preUpdate), - criteria, - updates?.['$set'], - ); + if (!updates) { + throw new Error('Unable to run findOneAndUpdate pre hook'); } + await repository.preUpdate(query, criteria, updates); + repository.emitter.emit( + repository.getEventName(EHook.preUpdate), + criteria, + updates?.['$set'], + ); }); hooks.updateMany.pre.execute(async function () { const query = this as Query; const criteria = query.getFilter(); const updates = query.getUpdate(); - if (updates) { - await repository.preUpdateMany(query, criteria, updates); - repository.emitter.emit( - repository.getEventName(EHook.preUpdateMany), - criteria, - updates?.['$set'], - ); + if (!updates) { + throw new Error('Unable to run run updateMany pre hook'); } + await repository.preUpdateMany(query, criteria, updates); + repository.emitter.emit( + repository.getEventName(EHook.preUpdateMany), + criteria, + updates?.['$set'], + ); }); hooks.updateMany.post.execute(async function (updated: any) { From aaa24656d3b1dd20477a5e0b9b52912cd8071cf5 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Fri, 20 Dec 2024 08:57:21 +0100 Subject: [PATCH 4/9] fix: throw if no updates has been provided to updateOne --- api/src/utils/generics/base-repository.ts | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index b8e127f7..c9343fdd 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -488,22 +488,24 @@ export abstract class BaseRepository< ); const filterCriteria = query.getFilter(); const queryUpdates = query.getUpdate(); - if (queryUpdates) { - await this.preUpdateValidate(filterCriteria, queryUpdates); - this.emitter.emit( - this.getEventName(EHook.preUpdateValidate), - filterCriteria, - queryUpdates, - ); - await this.postUpdateValidate(filterCriteria, queryUpdates); - this.emitter.emit( - this.getEventName(EHook.postUpdateValidate), - filterCriteria, - queryUpdates, - ); + if (!queryUpdates) { + throw new Error('updateOne() query updates are not undefined'); } + await this.preUpdateValidate(filterCriteria, queryUpdates); + this.emitter.emit( + this.getEventName(EHook.preUpdateValidate), + filterCriteria, + queryUpdates, + ); + + await this.postUpdateValidate(filterCriteria, queryUpdates); + this.emitter.emit( + this.getEventName(EHook.postUpdateValidate), + filterCriteria, + queryUpdates, + ); return await this.executeOne(query, this.cls); } From 34b42c005fe85819508fce19df436e1c4970395e Mon Sep 17 00:00:00 2001 From: Med Marrouchi Date: Mon, 30 Dec 2024 08:58:35 +0100 Subject: [PATCH 5/9] Update api/src/utils/generics/base-repository.ts --- api/src/utils/generics/base-repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index c9343fdd..6366e57c 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -193,7 +193,7 @@ export abstract class BaseRepository< const criteria = query.getFilter(); const updates = query.getUpdate(); if (!updates) { - throw new Error('Unable to run run updateMany pre hook'); + throw new Error('Unable to execute updateMany() pre-hook'); } await repository.preUpdateMany(query, criteria, updates); repository.emitter.emit( From ca3a0f364f4ba676ffc09efd9f9ef0cea7fb5471 Mon Sep 17 00:00:00 2001 From: Med Marrouchi Date: Mon, 30 Dec 2024 08:58:47 +0100 Subject: [PATCH 6/9] Update api/src/utils/generics/base-repository.ts --- api/src/utils/generics/base-repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 6366e57c..94f4f67c 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -490,7 +490,7 @@ export abstract class BaseRepository< const queryUpdates = query.getUpdate(); if (!queryUpdates) { - throw new Error('updateOne() query updates are not undefined'); + throw new Error(' Unable to execute updateOne() - No updates'); } await this.preUpdateValidate(filterCriteria, queryUpdates); From 7e98fdc3bbb44bc88f2ed48d2c8d6378c7541cd1 Mon Sep 17 00:00:00 2001 From: Med Marrouchi Date: Mon, 30 Dec 2024 08:58:56 +0100 Subject: [PATCH 7/9] Update api/src/utils/pagination/pagination-query.dto.ts --- api/src/utils/pagination/pagination-query.dto.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/utils/pagination/pagination-query.dto.ts b/api/src/utils/pagination/pagination-query.dto.ts index 80401ab2..31ac2ecf 100644 --- a/api/src/utils/pagination/pagination-query.dto.ts +++ b/api/src/utils/pagination/pagination-query.dto.ts @@ -16,7 +16,7 @@ export type QuerySortDto = [ ]; export type PageQueryDto = { - skip: number; - limit: number; - sort?: QuerySortDto; + skip: number | undefined; + limit: number | undefined; + sort: QuerySortDto; }; From 62e18ab591b1b90387c0a3b636451fc6a6634515 Mon Sep 17 00:00:00 2001 From: Med Marrouchi Date: Mon, 30 Dec 2024 08:59:31 +0100 Subject: [PATCH 8/9] Update api/src/utils/generics/base-repository.ts --- api/src/utils/generics/base-repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 94f4f67c..306dcd80 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -490,7 +490,7 @@ export abstract class BaseRepository< const queryUpdates = query.getUpdate(); if (!queryUpdates) { - throw new Error(' Unable to execute updateOne() - No updates'); + throw new Error('Unable to execute updateOne() - No updates'); } await this.preUpdateValidate(filterCriteria, queryUpdates); From d3123268aa1f644fbe89bb3a30fe177ff020b981 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Mon, 30 Dec 2024 10:33:33 +0100 Subject: [PATCH 9/9] fix: findOne and findById types --- api/src/utils/generics/base-repository.ts | 12 ++++++------ api/src/utils/generics/base-service.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 306dcd80..2ca8cfa3 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -241,7 +241,7 @@ export abstract class BaseRepository< query: Query, cls: new () => R, options?: ClassTransformOptions, - ): Promise { + ): Promise { const doc = await query.lean(this.leanOpts).exec(); return plainToClass(cls, doc, options ?? this.transformOpts); } @@ -256,8 +256,8 @@ export abstract class BaseRepository< } return typeof criteria === 'string' - ? this.model.findById(criteria, projection) - : this.model.findOne(criteria, projection); + ? this.model.findById>(criteria, projection) + : this.model.findOne>(criteria, projection); } async findOne( @@ -267,7 +267,7 @@ export abstract class BaseRepository< ) { if (!criteria) { // @TODO : Issue a warning ? - return Promise.resolve(undefined); + return Promise.resolve(null); } const query = this.findOneQuery(criteria, projection); @@ -277,7 +277,7 @@ export abstract class BaseRepository< async findOneAndPopulate( criteria: string | TFilterQuery, projection?: ProjectionType, - ): Promise { + ): Promise { this.ensureCanPopulate(); const query = this.findOneQuery(criteria, projection).populate( this.populate, @@ -474,7 +474,7 @@ export abstract class BaseRepository< async updateOne>( criteria: string | TFilterQuery, dto: UpdateQuery, - ): Promise { + ): Promise { const query = this.model.findOneAndUpdate( { ...(typeof criteria === 'string' ? { _id: criteria } : criteria), diff --git a/api/src/utils/generics/base-service.ts b/api/src/utils/generics/base-service.ts index 5faeaf4c..26eb8e56 100644 --- a/api/src/utils/generics/base-service.ts +++ b/api/src/utils/generics/base-service.ts @@ -33,7 +33,7 @@ export abstract class BaseService< criteria: string | TFilterQuery, options?: ClassTransformOptions, projection?: ProjectionType, - ): Promise { + ): Promise { return await this.repository.findOne(criteria, options, projection); } @@ -173,7 +173,7 @@ export abstract class BaseService< async updateOne>>( criteria: string | TFilterQuery, dto: D, - ): Promise { + ): Promise { return await this.repository.updateOne(criteria, dto); }