From af3aa929ed9638262d22af9384225033edaf6c4a Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Tue, 21 May 2024 00:18:11 +0200 Subject: [PATCH 1/9] refactor!: replace type map by a domain tree Also modified ESLint config. --- .eslintrc.cjs => eslint.config.cjs | 0 .../src/book.repository.ts | 9 +- .../nestjs-mongoose-book-manager/yarn.lock | 1 + package.json | 10 +- src/index.ts | 22 ++--- src/mongoose.repository.ts | 22 ++--- src/mongoose.transactional-repository.ts | 8 +- src/util/domain-model.ts | 85 +++++++++++++++++ src/util/type-map.ts | 95 ------------------- test/repository/auditable.book.repository.ts | 13 ++- test/repository/book.repository.ts | 11 ++- .../book.transactional-repository.ts | 9 +- 12 files changed, 140 insertions(+), 145 deletions(-) rename .eslintrc.cjs => eslint.config.cjs (100%) create mode 100644 src/util/domain-model.ts delete mode 100644 src/util/type-map.ts diff --git a/.eslintrc.cjs b/eslint.config.cjs similarity index 100% rename from .eslintrc.cjs rename to eslint.config.cjs diff --git a/examples/nestjs-mongoose-book-manager/src/book.repository.ts b/examples/nestjs-mongoose-book-manager/src/book.repository.ts index 297ed39..37e996f 100644 --- a/examples/nestjs-mongoose-book-manager/src/book.repository.ts +++ b/examples/nestjs-mongoose-book-manager/src/book.repository.ts @@ -24,9 +24,12 @@ export class MongooseBookRepository constructor(@InjectConnection() connection: Connection) { super( { - Default: { type: Book, schema: BookSchema }, - PaperBook: { type: PaperBook, schema: PaperBookSchema }, - AudioBook: { type: AudioBook, schema: AudioBookSchema }, + type: Book, + schema: BookSchema, + subtypes: [ + { type: PaperBook, schema: PaperBookSchema }, + { type: AudioBook, schema: AudioBookSchema }, + ], }, connection, ); diff --git a/examples/nestjs-mongoose-book-manager/yarn.lock b/examples/nestjs-mongoose-book-manager/yarn.lock index b998bff..6750e44 100644 --- a/examples/nestjs-mongoose-book-manager/yarn.lock +++ b/examples/nestjs-mongoose-book-manager/yarn.lock @@ -5551,6 +5551,7 @@ which@^2.0.1: isexe "^2.0.0" "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== diff --git a/package.json b/package.json index de6c300..7d86a4d 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,10 @@ "name": "monguito", "version": "5.1.1", "description": "MongoDB Abstract Repository implementation for Node.js", - "author": { - "name": "Josu Martinez", - "email": "josu.martinez@gmail.com" - }, + "author": "Josu Martinez ", "license": "MIT", "homepage": "https://github.com/josuto/monguito#readme", - "repository": { - "type": "git", - "url": "https://github.com/josuto/monguito.git" - }, + "repository": "https://github.com/josuto/monguito.git", "bugs": { "url": "https://github.com/josuto/monguito/issues", "email": "josu.martinez@gmail.com" diff --git a/src/index.ts b/src/index.ts index bdb2b67..0b8a634 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,13 @@ import { MongooseTransactionalRepository } from './mongoose.transactional-reposi import { PartialEntityWithId, Repository } from './repository'; import { TransactionalRepository } from './transactional-repository'; import { Auditable, AuditableClass, isAuditable } from './util/audit'; +import { + AbsConstructor, + Constructor, + DomainModel, + DomainTree, + DomainTypeData, +} from './util/domain-model'; import { Entity } from './util/entity'; import { IllegalArgumentException, @@ -26,14 +33,6 @@ import { SchemaPlugin, } from './util/schema'; import { runInTransaction, TransactionOptions } from './util/transaction'; -import { - AbsConstructor, - Constructor, - SubtypeData, - SubtypeMap, - SupertypeData, - TypeMap, -} from './util/type-map'; export { AbsConstructor, @@ -44,6 +43,9 @@ export { Constructor, DeleteAllOptions, DeleteByIdOptions, + DomainModel, + DomainTree, + DomainTypeData, Entity, extendSchema, FindAllOptions, @@ -60,12 +62,8 @@ export { SaveOptions, SchemaOptions, SchemaPlugin, - SubtypeData, - SubtypeMap, - SupertypeData, TransactionalRepository, TransactionOptions, - TypeMap, UndefinedConstructorException, ValidationException, }; diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 25736e8..11c8253 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -7,6 +7,7 @@ import mongoose, { import { Optional } from 'typescript-optional'; import { PartialEntityWithId, Repository } from './repository'; import { isAuditable } from './util/audit'; +import { Constructor, DomainModel, DomainTree } from './util/domain-model'; import { Entity } from './util/entity'; import { IllegalArgumentException, @@ -20,7 +21,6 @@ import { FindOneOptions, SaveOptions, } from './util/operation-options'; -import { Constructor, TypeMap, TypeMapImpl } from './util/type-map'; /** * Abstract Mongoose-based implementation of the {@link Repository} interface. @@ -28,25 +28,25 @@ import { Constructor, TypeMap, TypeMapImpl } from './util/type-map'; export abstract class MongooseRepository> implements Repository { - private readonly typeMap: TypeMapImpl; + private readonly domainTree: DomainTree; protected readonly entityModel: Model; /** * Sets up the underlying configuration to enable database operation execution. - * @param {TypeMap} typeMap a map of domain object types supported by this repository. + * @param {DomainModel} domainModel the domain model supported by this repository. * @param {Connection=} connection (optional) a MongoDB instance connection. */ protected constructor( - typeMap: TypeMap, + domainModel: DomainModel, protected readonly connection?: Connection, ) { - this.typeMap = new TypeMapImpl(typeMap); + this.domainTree = new DomainTree(domainModel); this.entityModel = this.createEntityModel(connection); } private createEntityModel(connection?: Connection) { let entityModel; - const supertypeData = this.typeMap.getSupertypeData(); + const supertypeData = this.domainTree.getSupertypeData(); if (connection) { entityModel = connection.model( supertypeData.type.name, @@ -58,7 +58,7 @@ export abstract class MongooseRepository> supertypeData.schema, ); } - for (const subtypeData of this.typeMap.getSubtypesData()) { + for (const subtypeData of this.domainTree.getSubtypeTree()) { entityModel.discriminator(subtypeData.type.name, subtypeData.schema); } return entityModel; @@ -212,12 +212,12 @@ export abstract class MongooseRepository> entity: S | PartialEntityWithId, ): void { const entityClassName = entity['constructor']['name']; - if (!this.typeMap.has(entityClassName)) { + if (!this.domainTree.has(entityClassName)) { throw new IllegalArgumentException( `The entity with name ${entityClassName} is not included in the setup of the custom repository`, ); } - const isSubtype = entityClassName !== this.typeMap.getSupertypeName(); + const isSubtype = entityClassName !== this.domainTree.getSupertypeName(); const hasEntityDiscriminatorKey = '__t' in entity; if (isSubtype && !hasEntityDiscriminatorKey) { entity['__t'] = entityClassName; @@ -304,8 +304,8 @@ export abstract class MongooseRepository> if (!document) return null; const entityKey = document.get('__t'); const constructor: Constructor | undefined = entityKey - ? (this.typeMap.getSubtypeData(entityKey)?.type as Constructor) - : (this.typeMap.getSupertypeData().type as Constructor); + ? (this.domainTree.getSubtypeData(entityKey)?.type as Constructor) + : (this.domainTree.getSupertypeData().type as Constructor); if (constructor) { // safe instantiation as no abstract class instance can be stored in the first place return new constructor(document.toObject()); diff --git a/src/mongoose.transactional-repository.ts b/src/mongoose.transactional-repository.ts index d9a8316..b847d55 100644 --- a/src/mongoose.transactional-repository.ts +++ b/src/mongoose.transactional-repository.ts @@ -2,6 +2,7 @@ import { ClientSession, Connection, UpdateQuery } from 'mongoose'; import { MongooseRepository } from './mongoose.repository'; import { PartialEntityWithId } from './repository'; import { TransactionalRepository } from './transactional-repository'; +import { DomainModel } from './util/domain-model'; import { Entity } from './util/entity'; import { IllegalArgumentException } from './util/exceptions'; import { @@ -10,7 +11,6 @@ import { SaveOptions, } from './util/operation-options'; import { runInTransaction } from './util/transaction'; -import { TypeMap } from './util/type-map'; /** * Abstract Mongoose-based implementation of the {@link TransactionalRepository} interface. @@ -23,11 +23,11 @@ export abstract class MongooseTransactionalRepository< { /** * Sets up the underlying configuration to enable database operation execution. - * @param {TypeMap} typeMap a map of domain object types supported by this repository. + * @param {DomainModel} domainModel the domain model supported by this repository. * @param {Connection=} connection (optional) a MongoDB instance connection. */ - protected constructor(typeMap: TypeMap, connection?: Connection) { - super(typeMap, connection); + protected constructor(domainModel: DomainModel, connection?: Connection) { + super(domainModel, connection); } /** @inheritdoc */ diff --git a/src/util/domain-model.ts b/src/util/domain-model.ts new file mode 100644 index 0000000..34f5af3 --- /dev/null +++ b/src/util/domain-model.ts @@ -0,0 +1,85 @@ +import { Schema } from 'mongoose'; +import { Entity } from './entity'; +import { IllegalArgumentException } from './exceptions'; + +/** + * Models a domain type instance constructor. + */ +export type Constructor = new (...args: any) => T; + +/** + * Models an abstract domain type instance constructor. + */ + +export type AbsConstructor = abstract new (...args: any) => T; + +/** + * Models some domain type data. + */ +export interface DomainTypeData { + type: Constructor | AbsConstructor; + schema: Schema; +} + +/** + * Models any subtype of a given type. + */ +type InferSubtype = T extends infer S ? S : never; + +/** + * Domain model specification. + */ +export interface DomainModel extends DomainTypeData { + subtypes?: DomainModel>[]; +} + +/** + * Domain model implementation. + */ +export class DomainTree implements DomainModel { + readonly type: Constructor | AbsConstructor; + readonly schema: Schema; + readonly subtypeTree?: DomainModel>[]; + + constructor(domainModel: DomainModel) { + if (!domainModel.type || !domainModel.schema) { + throw new IllegalArgumentException( + 'The given domain model must specify a type and a schema', + ); + } + this.type = domainModel.type; + this.schema = domainModel.schema; + this.subtypeTree = []; + for (const subtypeData of domainModel.subtypes ?? []) { + this.subtypeTree.push(new DomainTree(subtypeData)); + } + } + + getSubtypeData(type: string): DomainTypeData> | undefined { + const subtypeData = this.subtypeTree?.find( + (subtype) => subtype.type.name === type, + ); + if (subtypeData) + return { type: subtypeData?.type, schema: subtypeData?.schema }; + else return undefined; + } + + getSupertypeData(): DomainTypeData { + return { + type: this.type, + schema: this.schema, + }; + } + + getSupertypeName(): string { + return this.getSupertypeData().type.name; + } + + getSubtypeTree(): DomainModel>[] { + return this.subtypeTree || []; + } + + has(type: string): boolean { + return type === this.getSupertypeName() || !!this.getSubtypeData(type); + } +} diff --git a/src/util/type-map.ts b/src/util/type-map.ts deleted file mode 100644 index d667561..0000000 --- a/src/util/type-map.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Schema } from 'mongoose'; -import { Entity } from './entity'; -import { IllegalArgumentException } from './exceptions'; - -/** - * Models a domain object instance constructor. - */ -export type Constructor = new (...args: any) => T; - -/** - * Models an abstract domain object supertype instance constructor. - */ - -export type AbsConstructor = abstract new (...args: any) => T; - -/** - * Models some domain object subtype data. - */ -export type SubtypeData = { - type: Constructor; - schema: Schema; -}; - -/** - * Models some domain object supertype data. - */ -export type SupertypeData = { - type: Constructor | AbsConstructor; - schema: Schema; -}; - -/** - * Models a map of domain object subtypes. - */ -export type SubtypeMap = { - [key: string]: SubtypeData; -}; - -/** - * Models a map of domain object supertype and subtypes. - */ -export type TypeMap = - | { - ['Default']: SupertypeData; - } - | SubtypeMap; - -/** - * A `TypeMap` implementation designed to ease map content handling for its clients. - */ -export class TypeMapImpl { - readonly typeNames: string[]; - readonly supertypeData: SupertypeData; - readonly subtypeData: SubtypeData[]; - - constructor(map: TypeMap) { - if (!map.Default) { - throw new IllegalArgumentException( - 'The given map must include domain supertype data', - ); - } - this.supertypeData = map.Default as SupertypeData; - this.typeNames = Object.keys(map).filter((key) => key !== 'Default'); - this.subtypeData = Object.entries(map).reduce((accumulator, entry) => { - if (entry[0] !== 'Default') { - // @ts-expect-error - safe instantiation as any non-root map entry refers to some subtype data - accumulator.push(entry[1]); - } - return accumulator; - }, []); - } - - getSubtypeData(type: string): SubtypeData | undefined { - const index = this.typeNames.indexOf(type); - return index !== -1 ? this.subtypeData[index] : undefined; - } - - getSupertypeData(): SupertypeData { - return this.supertypeData; - } - - getSupertypeName(): string { - return this.getSupertypeData().type.name; - } - - getSubtypesData(): SubtypeData[] { - return this.subtypeData; - } - - has(type: string): boolean { - return ( - type === this.getSupertypeName() || this.typeNames.indexOf(type) !== -1 - ); - } -} diff --git a/test/repository/auditable.book.repository.ts b/test/repository/auditable.book.repository.ts index 9054ac9..95162d7 100644 --- a/test/repository/auditable.book.repository.ts +++ b/test/repository/auditable.book.repository.ts @@ -8,11 +8,14 @@ import { export class MongooseAuditableBookRepository extends MongooseRepository { constructor() { super({ - Default: { type: AuditableBook, schema: AuditableBookSchema }, - AuditablePaperBook: { - type: AuditablePaperBook, - schema: AuditablePaperBookSchema, - }, + type: AuditableBook, + schema: AuditableBookSchema, + subtypes: [ + { + type: AuditablePaperBook, + schema: AuditablePaperBookSchema, + }, + ], }); } } diff --git a/test/repository/book.repository.ts b/test/repository/book.repository.ts index fbe8c23..c7cdf7d 100644 --- a/test/repository/book.repository.ts +++ b/test/repository/book.repository.ts @@ -1,9 +1,9 @@ +import { Optional } from 'typescript-optional'; import { IllegalArgumentException, MongooseRepository, Repository, } from '../../src'; -import { Optional } from 'typescript-optional'; import { AudioBook, Book, PaperBook } from '../domain/book'; import { AudioBookSchema, BookSchema, PaperBookSchema } from './book.schema'; @@ -17,9 +17,12 @@ export class MongooseBookRepository { constructor() { super({ - Default: { type: Book, schema: BookSchema }, - PaperBook: { type: PaperBook, schema: PaperBookSchema }, - AudioBook: { type: AudioBook, schema: AudioBookSchema }, + type: Book, + schema: BookSchema, + subtypes: [ + { type: PaperBook, schema: PaperBookSchema }, + { type: AudioBook, schema: AudioBookSchema }, + ], }); } diff --git a/test/repository/book.transactional-repository.ts b/test/repository/book.transactional-repository.ts index 537c010..ffe7056 100644 --- a/test/repository/book.transactional-repository.ts +++ b/test/repository/book.transactional-repository.ts @@ -5,9 +5,12 @@ import { AudioBookSchema, BookSchema, PaperBookSchema } from './book.schema'; export class MongooseBookTransactionalRepository extends MongooseTransactionalRepository { constructor() { super({ - Default: { type: Book, schema: BookSchema }, - PaperBook: { type: PaperBook, schema: PaperBookSchema }, - AudioBook: { type: AudioBook, schema: AudioBookSchema }, + type: Book, + schema: BookSchema, + subtypes: [ + { type: PaperBook, schema: PaperBookSchema }, + { type: AudioBook, schema: AudioBookSchema }, + ], }); } } From ea6e274f58e70a0b7a2ffad05cec1976e4874583 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Fri, 24 May 2024 19:31:17 +0200 Subject: [PATCH 2/9] fix: delete incorrect InferSubtype --- src/util/domain-model.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/util/domain-model.ts b/src/util/domain-model.ts index 34f5af3..3baf3cf 100644 --- a/src/util/domain-model.ts +++ b/src/util/domain-model.ts @@ -18,19 +18,14 @@ export type AbsConstructor = abstract new (...args: any) => T; */ export interface DomainTypeData { type: Constructor | AbsConstructor; - schema: Schema; + schema: Schema; // FIXME: should be Schema; } -/** - * Models any subtype of a given type. - */ -type InferSubtype = T extends infer S ? S : never; - /** * Domain model specification. */ export interface DomainModel extends DomainTypeData { - subtypes?: DomainModel>[]; + subtypes?: DomainModel[]; } /** @@ -39,7 +34,7 @@ export interface DomainModel extends DomainTypeData { export class DomainTree implements DomainModel { readonly type: Constructor | AbsConstructor; readonly schema: Schema; - readonly subtypeTree?: DomainModel>[]; + readonly subtypeTree?: DomainModel[]; // FIXME: should be DomainTree[]; constructor(domainModel: DomainModel) { if (!domainModel.type || !domainModel.schema) { @@ -55,7 +50,7 @@ export class DomainTree implements DomainModel { } } - getSubtypeData(type: string): DomainTypeData> | undefined { + getSubtypeData(type: string): DomainTypeData | undefined { const subtypeData = this.subtypeTree?.find( (subtype) => subtype.type.name === type, ); @@ -75,7 +70,7 @@ export class DomainTree implements DomainModel { return this.getSupertypeData().type.name; } - getSubtypeTree(): DomainModel>[] { + getSubtypeTree(): DomainModel[] { return this.subtypeTree || []; } From aff10a7686d35620a40b7e01515cc8d7181b905a Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 25 May 2024 21:15:44 +0200 Subject: [PATCH 3/9] docs: add comment for community questioning purposes --- src/util/domain-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/domain-model.ts b/src/util/domain-model.ts index 3baf3cf..d9bcbff 100644 --- a/src/util/domain-model.ts +++ b/src/util/domain-model.ts @@ -25,7 +25,7 @@ export interface DomainTypeData { * Domain model specification. */ export interface DomainModel extends DomainTypeData { - subtypes?: DomainModel[]; + subtypes?: DomainModel[]; // FIXME: should be DomainModel[]; } /** From ba410c99bf74d03c0f49f37b505ffd5c75c96abd Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 25 May 2024 22:53:10 +0200 Subject: [PATCH 4/9] feat: prohibit abstract domain leaf types Also unpublish some domain model types to avoid API maintenance responsibilties. --- src/index.ts | 11 +-------- src/mongoose.repository.ts | 12 +++++----- src/util/domain-model.ts | 48 +++++++++++++++++++++++++++----------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0b8a634..142a041 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,13 +3,7 @@ import { MongooseTransactionalRepository } from './mongoose.transactional-reposi import { PartialEntityWithId, Repository } from './repository'; import { TransactionalRepository } from './transactional-repository'; import { Auditable, AuditableClass, isAuditable } from './util/audit'; -import { - AbsConstructor, - Constructor, - DomainModel, - DomainTree, - DomainTypeData, -} from './util/domain-model'; +import { DomainModel, DomainTree } from './util/domain-model'; import { Entity } from './util/entity'; import { IllegalArgumentException, @@ -35,17 +29,14 @@ import { import { runInTransaction, TransactionOptions } from './util/transaction'; export { - AbsConstructor, Auditable, AuditableClass, AuditableSchema, BaseSchema, - Constructor, DeleteAllOptions, DeleteByIdOptions, DomainModel, DomainTree, - DomainTypeData, Entity, extendSchema, FindAllOptions, diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 11c8253..6e4900b 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -7,7 +7,7 @@ import mongoose, { import { Optional } from 'typescript-optional'; import { PartialEntityWithId, Repository } from './repository'; import { isAuditable } from './util/audit'; -import { Constructor, DomainModel, DomainTree } from './util/domain-model'; +import { DomainModel, DomainTree } from './util/domain-model'; import { Entity } from './util/entity'; import { IllegalArgumentException, @@ -303,15 +303,15 @@ export abstract class MongooseRepository> ): S | null { if (!document) return null; const entityKey = document.get('__t'); - const constructor: Constructor | undefined = entityKey - ? (this.domainTree.getSubtypeData(entityKey)?.type as Constructor) - : (this.domainTree.getSupertypeData().type as Constructor); + const constructor = entityKey + ? this.domainTree.getSubtypeConstructor(entityKey) + : this.domainTree.getSupertypeConstructor(); if (constructor) { // safe instantiation as no abstract class instance can be stored in the first place - return new constructor(document.toObject()); + return new constructor(document.toObject()) as S; } throw new UndefinedConstructorException( - `There is no registered instance constructor for the document with ID ${document.id}`, + `There is no registered instance constructor for the document with ID ${document.id} or the constructor is abstract`, ); } } diff --git a/src/util/domain-model.ts b/src/util/domain-model.ts index d9bcbff..eb3db73 100644 --- a/src/util/domain-model.ts +++ b/src/util/domain-model.ts @@ -5,27 +5,40 @@ import { IllegalArgumentException } from './exceptions'; /** * Models a domain type instance constructor. */ -export type Constructor = new (...args: any) => T; +type Constructor = new (...args: any) => T; /** * Models an abstract domain type instance constructor. */ - -export type AbsConstructor = abstract new (...args: any) => T; +type AbsConstructor = abstract new (...args: any) => T; /** * Models some domain type data. */ -export interface DomainTypeData { +type DomainTypeData = { type: Constructor | AbsConstructor; - schema: Schema; // FIXME: should be Schema; -} + schema: Schema; +}; + +/** + * Models some domain leaf type data. + */ +type DomainLeafTypeData = { type: Constructor; schema: Schema }; + +/** + * Models some domain intermediate type data. + */ +type DomainIntermediateTypeData = { + type: Constructor | AbsConstructor; + schema: Schema; + subtypes: (DomainIntermediateTypeData | DomainLeafTypeData)[]; +}; /** * Domain model specification. */ export interface DomainModel extends DomainTypeData { - subtypes?: DomainModel[]; // FIXME: should be DomainModel[]; + subtypes?: (DomainIntermediateTypeData | DomainLeafTypeData)[]; } /** @@ -33,8 +46,8 @@ export interface DomainModel extends DomainTypeData { */ export class DomainTree implements DomainModel { readonly type: Constructor | AbsConstructor; - readonly schema: Schema; - readonly subtypeTree?: DomainModel[]; // FIXME: should be DomainTree[]; + readonly schema: Schema; + readonly subtypes: (DomainIntermediateTypeData | DomainLeafTypeData)[]; constructor(domainModel: DomainModel) { if (!domainModel.type || !domainModel.schema) { @@ -44,14 +57,14 @@ export class DomainTree implements DomainModel { } this.type = domainModel.type; this.schema = domainModel.schema; - this.subtypeTree = []; + this.subtypes = []; for (const subtypeData of domainModel.subtypes ?? []) { - this.subtypeTree.push(new DomainTree(subtypeData)); + this.subtypes.push(new DomainTree(subtypeData)); } } getSubtypeData(type: string): DomainTypeData | undefined { - const subtypeData = this.subtypeTree?.find( + const subtypeData = this.subtypes?.find( (subtype) => subtype.type.name === type, ); if (subtypeData) @@ -59,6 +72,11 @@ export class DomainTree implements DomainModel { else return undefined; } + getSubtypeConstructor(type: string): Constructor | undefined { + const subtypeData = this.getSubtypeData(type); + return subtypeData?.type as Constructor; + } + getSupertypeData(): DomainTypeData { return { type: this.type, @@ -66,12 +84,16 @@ export class DomainTree implements DomainModel { }; } + getSupertypeConstructor(): Constructor | undefined { + return this.type as Constructor; + } + getSupertypeName(): string { return this.getSupertypeData().type.name; } getSubtypeTree(): DomainModel[] { - return this.subtypeTree || []; + return this.subtypes || []; } has(type: string): boolean { From c3ede7f40f426ea98a3b29c3039393193a6b1f96 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 1 Jun 2024 11:56:46 +0200 Subject: [PATCH 5/9] fix: remove non-required code --- src/mongoose.repository.ts | 9 ++++----- src/util/domain-model.ts | 26 +++++++------------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 6e4900b..9192aa5 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -46,16 +46,15 @@ export abstract class MongooseRepository> private createEntityModel(connection?: Connection) { let entityModel; - const supertypeData = this.domainTree.getSupertypeData(); if (connection) { entityModel = connection.model( - supertypeData.type.name, - supertypeData.schema, + this.domainTree.type.name, + this.domainTree.schema, ); } else { entityModel = mongoose.model( - supertypeData.type.name, - supertypeData.schema, + this.domainTree.type.name, + this.domainTree.schema, ); } for (const subtypeData of this.domainTree.getSubtypeTree()) { diff --git a/src/util/domain-model.ts b/src/util/domain-model.ts index eb3db73..5d4347e 100644 --- a/src/util/domain-model.ts +++ b/src/util/domain-model.ts @@ -63,13 +63,12 @@ export class DomainTree implements DomainModel { } } - getSubtypeData(type: string): DomainTypeData | undefined { - const subtypeData = this.subtypes?.find( - (subtype) => subtype.type.name === type, - ); - if (subtypeData) - return { type: subtypeData?.type, schema: subtypeData?.schema }; - else return undefined; + getSubtypeTree(): DomainModel[] { + return this.subtypes || []; + } + + getSubtypeData(type: string): DomainModel | undefined { + return this.subtypes?.find((subtype) => subtype.type.name === type); } getSubtypeConstructor(type: string): Constructor | undefined { @@ -77,23 +76,12 @@ export class DomainTree implements DomainModel { return subtypeData?.type as Constructor; } - getSupertypeData(): DomainTypeData { - return { - type: this.type, - schema: this.schema, - }; - } - getSupertypeConstructor(): Constructor | undefined { return this.type as Constructor; } getSupertypeName(): string { - return this.getSupertypeData().type.name; - } - - getSubtypeTree(): DomainModel[] { - return this.subtypes || []; + return this.type.name; } has(type: string): boolean { From 36cb4d6f7a4df0e951139e11c63cb627aa7f852b Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 1 Jun 2024 17:08:04 +0200 Subject: [PATCH 6/9] fix!: remove connection from TransactionOptions The option connection is of no longer use in CRUD operations; only in runInTransaction. --- src/mongoose.repository.ts | 28 ------------------------ src/mongoose.transactional-repository.ts | 12 ---------- src/util/transaction.ts | 3 +-- 3 files changed, 1 insertion(+), 42 deletions(-) diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 9192aa5..93b083c 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -68,10 +68,6 @@ export abstract class MongooseRepository> id: string, options?: FindByIdOptions, ): Promise> { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (!id) throw new IllegalArgumentException('The given ID must be valid'); const document = await this.entityModel .findById(id) @@ -85,10 +81,6 @@ export abstract class MongooseRepository> filters: any, options?: FindOneOptions, ): Promise> { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (filters) console.warn( 'Since v5.0.1 the "filters" parameter is deprecated. Use "options.filters" instead.', @@ -104,10 +96,6 @@ export abstract class MongooseRepository> /** @inheritdoc */ async findAll(options?: FindAllOptions): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (options?.pageable?.pageNumber && options?.pageable?.pageNumber < 0) { throw new IllegalArgumentException( 'The given page number must be a positive number', @@ -143,10 +131,6 @@ export abstract class MongooseRepository> entity: S | PartialEntityWithId, options?: SaveOptions, ): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (!entity) throw new IllegalArgumentException( 'The given entity cannot be null or undefined', @@ -182,10 +166,6 @@ export abstract class MongooseRepository> entity: S, options?: SaveOptions, ): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (!entity) throw new IllegalArgumentException( 'The given entity cannot be null or undefined', @@ -245,10 +225,6 @@ export abstract class MongooseRepository> entity: PartialEntityWithId, options?: SaveOptions, ): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (!entity) throw new IllegalArgumentException('The given entity must be valid'); const document = await this.entityModel @@ -280,10 +256,6 @@ export abstract class MongooseRepository> /** @inheritdoc */ async deleteById(id: string, options?: DeleteByIdOptions): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (!id) throw new IllegalArgumentException('The given ID must be valid'); const isDeleted = await this.entityModel.findByIdAndDelete(id, { session: options?.session, diff --git a/src/mongoose.transactional-repository.ts b/src/mongoose.transactional-repository.ts index b847d55..a833e42 100644 --- a/src/mongoose.transactional-repository.ts +++ b/src/mongoose.transactional-repository.ts @@ -35,10 +35,6 @@ export abstract class MongooseTransactionalRepository< entities: (S | PartialEntityWithId)[], options?: SaveAllOptions, ): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); return await runInTransaction( async (session: ClientSession) => await Promise.all( @@ -56,10 +52,6 @@ export abstract class MongooseTransactionalRepository< /** @inheritdoc */ async deleteAll(options?: DeleteAllOptions): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); if (options?.filters === null) { throw new IllegalArgumentException('Null filters are disallowed'); } @@ -76,10 +68,6 @@ export abstract class MongooseTransactionalRepository< entity: PartialEntityWithId, options?: SaveOptions, ): Promise { - if (options?.connection) - console.warn( - 'Since v5.0.1 "options.connection" is deprecated as is of no longer use.', - ); const updateOperation = super.update.bind(this); return await runInTransaction( async (session: ClientSession) => diff --git a/src/util/transaction.ts b/src/util/transaction.ts index 69b86a6..21ae681 100644 --- a/src/util/transaction.ts +++ b/src/util/transaction.ts @@ -11,7 +11,6 @@ type DbCallback = (session: ClientSession) => Promise; * @property {ClientSession=} session (optional) a transaction session, required to run the operation within an existing transaction. */ export type TransactionOptions = { - connection?: Connection; session?: ClientSession; }; @@ -26,7 +25,7 @@ const MAX_RETRIES = 3; */ export async function runInTransaction( callback: DbCallback, - options?: TransactionOptions, + options?: TransactionOptions & { connection?: Connection }, ): Promise { if (options?.session) return callback(options.session); return await recursiveRunIntransaction(callback, 0, options?.connection); From 9a34d9f73221daa499be377841c499d045934278 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 1 Jun 2024 18:01:25 +0200 Subject: [PATCH 7/9] fix!: change findOne semantics This operation now returns an arbitrary existing entity (if any) --- src/mongoose.repository.ts | 13 +---- src/repository.ts | 6 +-- test/repository/book.repository.test.ts | 71 ++++++++++++++++--------- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 93b083c..362a95f 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -77,18 +77,9 @@ export abstract class MongooseRepository> } /** @inheritdoc */ - async findOne( - filters: any, - options?: FindOneOptions, - ): Promise> { - if (filters) - console.warn( - 'Since v5.0.1 the "filters" parameter is deprecated. Use "options.filters" instead.', - ); - if (!filters && !options?.filters) - throw new IllegalArgumentException('Missing search criteria (filters)'); + async findOne(options?: FindOneOptions): Promise> { const document = await this.entityModel - .findOne(options?.filters ?? filters) + .findOne(options?.filters ?? undefined) .session(options?.session ?? null) .exec(); return Optional.ofNullable(this.instantiateFrom(document) as S); diff --git a/src/repository.ts b/src/repository.ts index 5982caf..ad40328 100644 --- a/src/repository.ts +++ b/src/repository.ts @@ -35,15 +35,11 @@ export interface Repository { /** * Finds an entity by some filters. - * @param {any} filters some filters for the search - Deprecated since v5.0.1, use options.filters instead. * @param {FindOneOptions=} options (optional) search operation options. * @returns {Promise>} the entity or null. * @throws {IllegalArgumentException} if the given `filters` parameter is `undefined` or `null`. */ - findOne: ( - filters: any, - options?: FindOneOptions, - ) => Promise>; + findOne: (options?: FindOneOptions) => Promise>; /** * Finds all entities. diff --git a/test/repository/book.repository.test.ts b/test/repository/book.repository.test.ts index 27eab56..a49a1af 100644 --- a/test/repository/book.repository.test.ts +++ b/test/repository/book.repository.test.ts @@ -81,31 +81,17 @@ describe('Given an instance of book repository', () => { }); }); - describe('when searching a book by some filters', () => { - describe('by an undefined filter', () => { - it('throws an exception', async () => { - await expect( - bookRepository.findOne(undefined as unknown as string), - ).rejects.toThrow(IllegalArgumentException); - }); - }); - - describe('by a null filter', () => { - it('throws an exception', async () => { - await expect( - bookRepository.findOne(null as unknown as string), - ).rejects.toThrow(IllegalArgumentException); - }); - }); - - describe('by a filter matching no book', () => { - it('retrieves an empty book', async () => { - const book = await bookRepository.findOne({ title: 'The Hobbit' }); - expect(book).toEqual(Optional.empty()); + describe('when searching one book', () => { + describe('and there is no book stored', () => { + describe('not using any filter', () => { + it('retrieves an empty book', async () => { + const book = await bookRepository.findOne(); + expect(book).toEqual(Optional.empty()); + }); }); }); - describe('by a filter matching one or more books', () => { + describe('and there are books stored', () => { beforeEach(async () => { const paperBookToStore = paperBookFixture(); const audioBookToStore = audioBookFixture(); @@ -131,20 +117,53 @@ describe('Given an instance of book repository', () => { }); }); - describe('by a filter matching one book', () => { + describe('not using any filter', () => { + it('returns an arbitrary book', async () => { + const book = await bookRepository.findOne(); + expect(book.isPresent()).toBe(true); + expect([storedPaperBook, storedAudioBook]).toContainEqual(book.get()); + }); + }); + + describe('using an undefined filter', () => { + it('returns an arbitrary book', async () => { + const book = await bookRepository.findOne({ filters: undefined }); + expect(book.isPresent()).toBe(true); + expect([storedPaperBook, storedAudioBook]).toContainEqual(book.get()); + }); + }); + + describe('using a null filter', () => { + it('returns an arbitrary book', async () => { + const book = await bookRepository.findOne({ filters: null }); + expect(book.isPresent()).toBe(true); + expect([storedPaperBook, storedAudioBook]).toContainEqual(book.get()); + }); + }); + + describe('using a filter matching no book', () => { + it('retrieves an empty book', async () => { + const book = await bookRepository.findOne({ + filters: { title: 'The Hobbit' }, + }); + expect(book).toEqual(Optional.empty()); + }); + }); + + describe('using a filter matching one book', () => { it('retrieves the book', async () => { const book = await bookRepository.findOne({ - title: storedPaperBook.title, + filters: { title: storedPaperBook.title }, }); expect(book.isPresent()).toBe(true); expect(book.get()).toEqual(storedPaperBook); }); }); - describe('by a filter matching several books', () => { + describe('using a filter matching several books', () => { it('retrieves the first book inserted', async () => { const book = await bookRepository.findOne({ - title: { $exists: true }, + filters: { title: { $exists: true } }, }); expect(book.isPresent()).toBe(true); expect(book.get()).toEqual(storedPaperBook); From 6dbbad8a6cc518f1843fb140037ebdf39088d0c4 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 1 Jun 2024 18:11:06 +0200 Subject: [PATCH 8/9] fix!: enable deletion of all entities if options.filters of deleteAll is null --- src/mongoose.transactional-repository.ts | 4 ---- test/repository/book.transactional-repository.test.ts | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/mongoose.transactional-repository.ts b/src/mongoose.transactional-repository.ts index a833e42..5e12dce 100644 --- a/src/mongoose.transactional-repository.ts +++ b/src/mongoose.transactional-repository.ts @@ -4,7 +4,6 @@ import { PartialEntityWithId } from './repository'; import { TransactionalRepository } from './transactional-repository'; import { DomainModel } from './util/domain-model'; import { Entity } from './util/entity'; -import { IllegalArgumentException } from './util/exceptions'; import { DeleteAllOptions, SaveAllOptions, @@ -52,9 +51,6 @@ export abstract class MongooseTransactionalRepository< /** @inheritdoc */ async deleteAll(options?: DeleteAllOptions): Promise { - if (options?.filters === null) { - throw new IllegalArgumentException('Null filters are disallowed'); - } return await runInTransaction( async (session: ClientSession) => (await this.entityModel.deleteMany(options?.filters, { session })) diff --git a/test/repository/book.transactional-repository.test.ts b/test/repository/book.transactional-repository.test.ts index 4af735c..99dee70 100644 --- a/test/repository/book.transactional-repository.test.ts +++ b/test/repository/book.transactional-repository.test.ts @@ -491,13 +491,12 @@ describe('Given an instance of book repository', () => { }); describe('that includes a null filter', () => { - it('throws an exception', async () => { - await expect( - bookRepository.deleteAll({ filters: null as unknown as object }), - ).rejects.toThrow(IllegalArgumentException); + it('deletes all books', async () => { + const deletedBooks = await bookRepository.deleteAll(); + expect(deletedBooks).toBe(2); const storedBooks = await bookRepository.findAll(); - expect(storedBooks).toEqual([storedBook, storedPaperBook]); + expect(storedBooks.length).toBe(0); }); }); From f884fd7797a99749753a416ca975168fa5f126f5 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 1 Jun 2024 19:31:22 +0200 Subject: [PATCH 9/9] fix!: improve DevEx by further constraining types of some ops options FindAllOptions.sortBy and filters attribute from FindOneOptions, FindAllOptions, and DeleteAllOptions used to be of type 'any' . Now devs will experience TS errors when they do not provide valid values. --- src/mongoose.repository.ts | 9 +++-- src/mongoose.transactional-repository.ts | 2 +- src/repository.ts | 4 +- src/transactional-repository.ts | 2 +- src/util/operation-options.ts | 25 ++++++++----- test/repository/book.repository.test.ts | 37 ++++++++++++------- .../book.transactional-repository.test.ts | 4 +- 7 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 362a95f..3e76add 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -1,5 +1,6 @@ import mongoose, { Connection, + FilterQuery, HydratedDocument, Model, UpdateQuery, @@ -77,7 +78,9 @@ export abstract class MongooseRepository> } /** @inheritdoc */ - async findOne(options?: FindOneOptions): Promise> { + async findOne( + options?: FindOneOptions, + ): Promise> { const document = await this.entityModel .findOne(options?.filters ?? undefined) .session(options?.session ?? null) @@ -86,7 +89,7 @@ export abstract class MongooseRepository> } /** @inheritdoc */ - async findAll(options?: FindAllOptions): Promise { + async findAll(options?: FindAllOptions): Promise { if (options?.pageable?.pageNumber && options?.pageable?.pageNumber < 0) { throw new IllegalArgumentException( 'The given page number must be a positive number', @@ -102,7 +105,7 @@ export abstract class MongooseRepository> const pageNumber = options?.pageable?.pageNumber ?? 0; try { const documents = await this.entityModel - .find(options?.filters) + .find(options?.filters as FilterQuery) .skip(pageNumber > 0 ? (pageNumber - 1) * offset : 0) .limit(offset) .sort(options?.sortBy) diff --git a/src/mongoose.transactional-repository.ts b/src/mongoose.transactional-repository.ts index 5e12dce..4d01380 100644 --- a/src/mongoose.transactional-repository.ts +++ b/src/mongoose.transactional-repository.ts @@ -50,7 +50,7 @@ export abstract class MongooseTransactionalRepository< } /** @inheritdoc */ - async deleteAll(options?: DeleteAllOptions): Promise { + async deleteAll(options?: DeleteAllOptions): Promise { return await runInTransaction( async (session: ClientSession) => (await this.entityModel.deleteMany(options?.filters, { session })) diff --git a/src/repository.ts b/src/repository.ts index ad40328..d90af52 100644 --- a/src/repository.ts +++ b/src/repository.ts @@ -39,7 +39,7 @@ export interface Repository { * @returns {Promise>} the entity or null. * @throws {IllegalArgumentException} if the given `filters` parameter is `undefined` or `null`. */ - findOne: (options?: FindOneOptions) => Promise>; + findOne: (options?: FindOneOptions) => Promise>; /** * Finds all entities. @@ -47,7 +47,7 @@ export interface Repository { * @returns {Promise} all entities. * @throws {IllegalArgumentException} if the given `options` specifies an invalid parameter. */ - findAll: (options?: FindAllOptions) => Promise; + findAll: (options?: FindAllOptions) => Promise; /** * Saves (insert or update) an entity. diff --git a/src/transactional-repository.ts b/src/transactional-repository.ts index 9c63c9a..0c5ec2f 100644 --- a/src/transactional-repository.ts +++ b/src/transactional-repository.ts @@ -28,5 +28,5 @@ export interface TransactionalRepository * @returns {number} the number of deleted entities. * @see {@link DeleteAllOptions} */ - deleteAll: (options?: DeleteAllOptions) => Promise; + deleteAll: (options?: DeleteAllOptions) => Promise; } diff --git a/src/util/operation-options.ts b/src/util/operation-options.ts index 3115145..0efb8e8 100644 --- a/src/util/operation-options.ts +++ b/src/util/operation-options.ts @@ -1,3 +1,4 @@ +import { FilterQuery } from 'mongoose'; import { IllegalArgumentException } from './exceptions'; import { TransactionOptions } from './transaction'; @@ -44,15 +45,19 @@ export class Pageable { } } +export type SortOrder = { + [key: string]: 'asc' | 'desc' | 'ascending' | 'descending' | 1 | -1; +}; + /** * Specifies options for the `findAll` operation. - * @property {any=} filters (optional) some filters for the search. - * @property {any=} sortBy (optional) the sorting criteria for the search. + * @property {FilterQuery=} filters (optional) some filters for the search. + * @property {string | SortOrder=} sortBy (optional) the sorting criteria for the search. * @property {Pageable=} pageable (optional) paging configuration. */ -export type FindAllOptions = { - filters?: any; - sortBy?: any; +export type FindAllOptions = { + filters?: FilterQuery; + sortBy?: string | SortOrder; pageable?: Pageable; } & TransactionOptions; @@ -64,8 +69,8 @@ export type FindByIdOptions = TransactionOptions; /** * Specifies options for the `findOne` operation; */ -export type FindOneOptions = { - filters?: any; +export type FindOneOptions = { + filters?: FilterQuery; } & TransactionOptions; /** @@ -80,10 +85,10 @@ export type SaveAllOptions = AuditOptions & TransactionOptions; /** * Specifies options for the `deleteAll` operation. - * @property {any=} filters (optional) a MongoDB query object to select the entities to be deleted. + * @property {FilterQuery=} filters (optional) a MongoDB query object to select the entities to be deleted. */ -export type DeleteAllOptions = { - filters?: any; +export type DeleteAllOptions = { + filters?: FilterQuery; } & TransactionOptions; /** diff --git a/test/repository/book.repository.test.ts b/test/repository/book.repository.test.ts index a49a1af..7f2fb54 100644 --- a/test/repository/book.repository.test.ts +++ b/test/repository/book.repository.test.ts @@ -135,7 +135,9 @@ describe('Given an instance of book repository', () => { describe('using a null filter', () => { it('returns an arbitrary book', async () => { - const book = await bookRepository.findOne({ filters: null }); + const book = await bookRepository.findOne({ + filters: null as unknown as object, + }); expect(book.isPresent()).toBe(true); expect([storedPaperBook, storedAudioBook]).toContainEqual(book.get()); }); @@ -277,7 +279,7 @@ describe('Given an instance of book repository', () => { describe('and such a value refers to an existing field in some Book type', () => { it('retrieves a list with all books matching the filter', async () => { - const filters = { __t: 'PaperBook' }; + const filters = { title: 'Effective Java' }; const books = await bookRepository.findAll({ filters }); expect(books.length).toBe(1); expect(books).toEqual([storedPaperBook]); @@ -286,19 +288,29 @@ describe('Given an instance of book repository', () => { }); describe('and providing a value for the sort parameter', () => { - describe('and such a value is invalid', () => { - it('throws an exception', async () => { - const sortBy = { title: 2 }; - await expect(bookRepository.findAll({ sortBy })).rejects.toThrow( - IllegalArgumentException, - ); + describe('and such a value is the name of a book property', () => { + it('retrieves an ordered list with books', async () => { + const books = await bookRepository.findAll({ + sortBy: 'title', + }); + expect(books.length).toBe(3); + expect(books).toEqual([storedBook, storedPaperBook, storedAudioBook]); + }); + }); + + describe('and such a value is ascendent on a book property', () => { + it('retrieves an ordered list with books', async () => { + const books = await bookRepository.findAll({ + sortBy: { title: 'asc' }, + }); + expect(books.length).toBe(3); + expect(books).toEqual([storedBook, storedPaperBook, storedAudioBook]); }); }); - describe('and such a value is valid', () => { + describe('and such a value is descendent on a book property', () => { it('retrieves an ordered list with books', async () => { - const sortBy = { title: -1 }; - const books = await bookRepository.findAll({ sortBy }); + const books = await bookRepository.findAll({ sortBy: { title: -1 } }); expect(books.length).toBe(3); expect(books).toEqual([storedAudioBook, storedPaperBook, storedBook]); }); @@ -936,11 +948,10 @@ describe('Given an instance of book repository', () => { describe('and providing a valid value for all optional parameters', () => { it('retrieves an ordered list with books matching the filter', async () => { const filters = { __t: ['PaperBook', 'AudioBook'] }; - const sortBy = { title: -1 }; const pageable = { pageNumber: 1, offset: 1 }; const books = await bookRepository.findAll({ filters, - sortBy, + sortBy: { title: -1 }, pageable, }); expect(books.length).toBe(1); diff --git a/test/repository/book.transactional-repository.test.ts b/test/repository/book.transactional-repository.test.ts index 99dee70..d41e50a 100644 --- a/test/repository/book.transactional-repository.test.ts +++ b/test/repository/book.transactional-repository.test.ts @@ -492,7 +492,9 @@ describe('Given an instance of book repository', () => { describe('that includes a null filter', () => { it('deletes all books', async () => { - const deletedBooks = await bookRepository.deleteAll(); + const deletedBooks = await bookRepository.deleteAll({ + filters: null as unknown as object, + }); expect(deletedBooks).toBe(2); const storedBooks = await bookRepository.findAll();