diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index c286dae0..2ca8cfa3 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -81,7 +81,7 @@ export abstract class BaseRepository< readonly model: Model, private readonly cls: new () => T, protected readonly populate: P[] = [], - protected readonly clsPopulate: new () => TFull = undefined, + protected readonly clsPopulate?: new () => TFull, ) { this.registerLifeCycleHooks(); } @@ -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); @@ -151,13 +158,13 @@ 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); }); - hooks?.deleteMany.post.execute(async function (result: DeleteResult) { + hooks.deleteMany.post.execute(async function (result: DeleteResult) { repository.emitter.emit( repository.getEventName(EHook.postDelete), result, @@ -166,11 +173,13 @@ 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(); - + if (!updates) { + throw new Error('Unable to run findOneAndUpdate pre hook'); + } await repository.preUpdate(query, criteria, updates); repository.emitter.emit( repository.getEventName(EHook.preUpdate), @@ -179,11 +188,13 @@ export abstract class BaseRepository< ); }); - 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(); - + if (!updates) { + throw new Error('Unable to execute updateMany() pre-hook'); + } await repository.preUpdateMany(query, criteria, updates); repository.emitter.emit( repository.getEventName(EHook.preUpdateMany), @@ -192,7 +203,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( @@ -201,7 +212,7 @@ export abstract class BaseRepository< ); }); - hooks?.findOneAndUpdate.post.execute(async function ( + hooks.findOneAndUpdate.post.execute(async function ( updated: HydratedDocument, ) { if (updated) { @@ -227,10 +238,10 @@ export abstract class BaseRepository< } protected async executeOne>( - query: Query, + query: Query, cls: new () => R, options?: ClassTransformOptions, - ): Promise { + ): Promise { const doc = await query.lean(this.leanOpts).exec(); return plainToClass(cls, doc, options ?? this.transformOpts); } @@ -238,15 +249,15 @@ export abstract class BaseRepository< 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'); } 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( @@ -256,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); @@ -266,12 +277,12 @@ export abstract class BaseRepository< async findOneAndPopulate( criteria: string | TFilterQuery, projection?: ProjectionType, - ): Promise { + ): Promise { this.ensureCanPopulate(); const query = this.findOneQuery(criteria, projection).populate( this.populate, ); - return await this.executeOne(query, this.clsPopulate); + return await this.executeOne(query, this.clsPopulate!); } protected findQuery( @@ -371,13 +382,13 @@ export abstract class BaseRepository< const query = this.findQuery(filters, pageQuery, projection).populate( this.populate, ); - return await this.execute(query, this.clsPopulate); + return await this.execute(query, this.clsPopulate!); } const query = this.findQuery(filters, pageQuery, projection).populate( this.populate, ); - return await this.execute(query, this.clsPopulate); + return await this.execute(query, this.clsPopulate!); } protected findAllQuery( @@ -393,7 +404,7 @@ 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); + return await this.execute(query, this.clsPopulate!); } /** @@ -431,7 +442,7 @@ export abstract class BaseRepository< const query = this.findPageQuery(filters, pageQuery).populate( this.populate, ); - return await this.execute(query, this.clsPopulate); + return await this.execute(query, this.clsPopulate!); } async countAll(): Promise { @@ -463,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), @@ -478,6 +489,10 @@ export abstract class BaseRepository< const filterCriteria = query.getFilter(); const queryUpdates = query.getUpdate(); + if (!queryUpdates) { + throw new Error('Unable to execute updateOne() - No updates'); + } + await this.preUpdateValidate(filterCriteria, queryUpdates); this.emitter.emit( this.getEventName(EHook.preUpdateValidate), @@ -491,7 +506,6 @@ export abstract class BaseRepository< filterCriteria, queryUpdates, ); - return await this.executeOne(query, this.cls); } 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); } 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]); });