From 86ec9e5e53900a91a50008ebfde29d5c6a8eb2d2 Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Sun, 30 Apr 2023 17:31:29 -0400 Subject: [PATCH] feat(models): `Repository` Signed-off-by: Lexus Drumgold --- .dictionary.txt | 1 + __fixtures__/.gitkeep | 0 __fixtures__/account.entity.ts | 113 ++++++++++++ __fixtures__/account.factory.ts | 56 ++++++ __fixtures__/subscriber.entity.ts | 139 ++++++++++++++ __fixtures__/subscriber.factory.ts | 61 +++++++ __tests__/setup/chai.ts | 3 + package.json | 9 +- src/database/database.module.ts | 5 + .../repository.model.functional.spec.ts | 84 +++++++++ .../__tests__/repository.model.spec-d.ts | 14 ++ src/models/__tests__/repository.model.spec.ts | 80 +++++++++ src/models/index.ts | 1 + src/models/repository.model.ts | 170 ++++++++++++++++++ yarn.lock | 72 +++++--- 15 files changed, 778 insertions(+), 30 deletions(-) delete mode 100644 __fixtures__/.gitkeep create mode 100644 __fixtures__/account.entity.ts create mode 100644 __fixtures__/account.factory.ts create mode 100644 __fixtures__/subscriber.entity.ts create mode 100644 __fixtures__/subscriber.factory.ts create mode 100644 src/models/__tests__/repository.model.functional.spec.ts create mode 100644 src/models/__tests__/repository.model.spec-d.ts create mode 100644 src/models/__tests__/repository.model.spec.ts create mode 100644 src/models/repository.model.ts diff --git a/.dictionary.txt b/.dictionary.txt index 10852ae..9e12ff2 100644 --- a/.dictionary.txt +++ b/.dictionary.txt @@ -14,6 +14,7 @@ dessant dohm dotenv dtos +embeddable fbca gcloud ggshield diff --git a/__fixtures__/.gitkeep b/__fixtures__/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/__fixtures__/account.entity.ts b/__fixtures__/account.entity.ts new file mode 100644 index 0000000..fc5e347 --- /dev/null +++ b/__fixtures__/account.entity.ts @@ -0,0 +1,113 @@ +/** + * @file Fixtures - Account + * @module fixtures/Account + */ + +import { IsNullable, IsOptional, IsTimestamp } from '#src/decorators' +import type { IEntity } from '#src/interfaces' +import { isNIL, type Nullable } from '@flex-development/tutils' +import { + Entity, + PrimaryKey, + Property, + SerializedPrimaryKey, + Unique, + type EntityData +} from '@mikro-orm/core' +import { ObjectId } from '@mikro-orm/mongodb' +import { IsEmail, IsInstance, IsLowercase, IsMongoId } from 'class-validator' +import plur from 'plur' + +/** + * Data used to create a new account via the entity constructor. + */ +type AccountDTO = Omit, 'email'> & { email: string } + +/** + * Data model representing an entity in the accounts collection. + * + * @class + * @implements {IEntity} + */ +@Entity({ collection: plur(Account.name).toLowerCase() }) +class Account implements IEntity { + /** + * BSON object id. + * + * @public + * @readonly + * @member {ObjectId} _id + */ + @PrimaryKey({ type: ObjectId }) + @IsInstance(ObjectId) + @IsOptional() + public _id!: ObjectId + + /** + * Unix timestamp indicating when account was created. + * + * @public + * @readonly + * @member {number} created_at + */ + @Property({ type: 'integer' }) + @IsTimestamp() + public created_at: number = Date.now() + + /** + * Account email address. + * + * @public + * @readonly + * @member {Lowercase} email + */ + @Property({ type: 'string' }) + @Unique({ name: 'email' }) + @IsEmail() + @IsLowercase() + public email!: Lowercase + + /** + * Unique identifier for account. + * + * @public + * @readonly + * @member {string} id + */ + @SerializedPrimaryKey({ type: 'string' }) + @IsMongoId() + @IsOptional() + public id!: string + + /** + * Unix timestamp indicating when account was last modified. + * + * @public + * @readonly + * @member {Nullable} updated_at + */ + @Property({ nullable: true, type: 'integer' }) + @IsTimestamp() + @IsNullable() + public updated_at: Nullable = null + + /** + * Creates a new account entity. + * + * @param {AccountDTO} dto - Data transfer object + * @param {string} dto.email - Account email + */ + constructor({ _id, created_at, email, updated_at }: AccountDTO) { + if (!isNIL(_id)) { + this._id = _id + this.id = this._id.toString() + } + + !isNIL(created_at) && (this.created_at = created_at) + !isNIL(updated_at) && (this.updated_at = updated_at) + + this.email = email.trim().toLowerCase() + } +} + +export default Account diff --git a/__fixtures__/account.factory.ts b/__fixtures__/account.factory.ts new file mode 100644 index 0000000..ec97681 --- /dev/null +++ b/__fixtures__/account.factory.ts @@ -0,0 +1,56 @@ +/** + * @file Fixtures - AccountFactory + * @module fixtures/AccountFactory + */ + +import type { Constructor } from '@mikro-orm/core' +import type { MongoEntityManager } from '@mikro-orm/mongodb' +import { Factory, Faker } from '@mikro-orm/seeder' +import Account from './account.entity' + +/** + * Account entity factory. + * + * @see {@linkcode Account} + * + * @class + * @extends {Factory} + */ +class AccountFactory extends Factory { + /** + * Entity the factory generates entity instances for. + * + * @public + * @readonly + * @member {Constructor} model + */ + public readonly model: Constructor + + /** + * Creates a new {@linkcode Account} entity factory. + * + * @param {MongoEntityManager} em - Entity manager + */ + constructor(em: MongoEntityManager) { + super(em) + this.model = Account + } + + /** + * Returns the default set of attribute values that should be applied when + * creating an {@linkcode Account} entity. + * + * @see https://fakerjs.dev + * + * @protected + * @override + * + * @param {Faker} faker - Faker library + * @return {{ email: Account['email'] }} Account entity data + */ + protected override definition(faker: Faker): { email: Account['email'] } { + return { email: faker.internet.email().toLowerCase() } + } +} + +export default AccountFactory diff --git a/__fixtures__/subscriber.entity.ts b/__fixtures__/subscriber.entity.ts new file mode 100644 index 0000000..ac2453b --- /dev/null +++ b/__fixtures__/subscriber.entity.ts @@ -0,0 +1,139 @@ +/** + * @file Fixtures - Subscriber + * @module fixtures/Subscriber + */ + +import { IsNullable, IsOptional, IsTimestamp } from '#src/decorators' +import type { IEntity } from '#src/interfaces' +import { isNIL, type Nullable } from '@flex-development/tutils' +import { + Entity, + Index, + PrimaryKey, + Property, + SerializedPrimaryKey, + Unique, + type EntityData +} from '@mikro-orm/core' +import { ObjectId } from '@mikro-orm/mongodb' +import { + IsEmail, + IsInstance, + IsLowercase, + IsMongoId, + IsString, + MinLength +} from 'class-validator' +import plur from 'plur' + +/** + * Data used to create a new subscriber via the entity constructor. + */ +type SubscriberDTO = Omit, 'email' | 'name'> & { + email: string + name: string +} + +/** + * Data model representing an entity in the subscribers collection. + * + * @class + * @implements {IEntity} + */ +@Entity({ collection: plur(Subscriber.name).toLowerCase() }) +class Subscriber implements IEntity { + /** + * BSON object id. + * + * @public + * @readonly + * @member {ObjectId} _id + */ + @PrimaryKey({ type: ObjectId }) + @IsInstance(ObjectId) + @IsOptional() + public _id!: ObjectId + + /** + * Unix timestamp indicating when subscriber signed up. + * + * @public + * @readonly + * @member {number} created_at + */ + @Property({ type: 'integer' }) + @IsTimestamp() + public created_at: number = Date.now() + + /** + * Subscriber email address. + * + * @public + * @readonly + * @member {Lowercase} email + */ + @Property({ type: 'string' }) + @Unique({ name: 'email' }) + @IsEmail() + @IsLowercase() + public email!: Lowercase + + /** + * Unique identifier for subscriber. + * + * @public + * @readonly + * @member {string} id + */ + @SerializedPrimaryKey({ type: 'string' }) + @IsMongoId() + @IsOptional() + public id!: string + + /** + * Subscriber name. + * + * @public + * @readonly + * @member {string} name + */ + @Property({ type: 'string' }) + @Index({ name: 'name' }) + @IsString() + @MinLength(1) + public name!: string + + /** + * Unix timestamp indicating when subscriber was last modified. + * + * @public + * @readonly + * @member {Nullable} updated_at + */ + @Property({ nullable: true, type: 'integer' }) + @IsTimestamp() + @IsNullable() + public updated_at: Nullable = null + + /** + * Creates a new subscriber entity. + * + * @param {SubscriberDTO} dto - Data transfer object + * @param {string} dto.email - Subscriber email + * @param {string} dto.name - Subscriber name + */ + constructor({ _id, created_at, name, email, updated_at }: SubscriberDTO) { + if (!isNIL(_id)) { + this._id = _id + this.id = this._id.toString() + } + + !isNIL(created_at) && (this.created_at = created_at) + !isNIL(updated_at) && (this.updated_at = updated_at) + + this.email = email.trim().toLowerCase() + this.name = name.trim() + } +} + +export default Subscriber diff --git a/__fixtures__/subscriber.factory.ts b/__fixtures__/subscriber.factory.ts new file mode 100644 index 0000000..e2abaa5 --- /dev/null +++ b/__fixtures__/subscriber.factory.ts @@ -0,0 +1,61 @@ +/** + * @file Fixtures - SubscriberFactory + * @module fixtures/SubscriberFactory + */ + +import type { Constructor } from '@mikro-orm/core' +import type { MongoEntityManager } from '@mikro-orm/mongodb' +import { Factory, Faker } from '@mikro-orm/seeder' +import Subscriber from './subscriber.entity' + +/** + * Subscriber entity factory. + * + * @see {@linkcode Subscriber} + * + * @class + * @extends {Factory} + */ +class SubscriberFactory extends Factory { + /** + * Entity the factory generates entity instances for. + * + * @public + * @readonly + * @member {Constructor} model + */ + public readonly model: Constructor + + /** + * Creates a new {@linkcode Subscriber} entity factory. + * + * @param {MongoEntityManager} em - Entity manager + */ + constructor(em: MongoEntityManager) { + super(em) + this.model = Subscriber + } + + /** + * Returns the default set of attribute values that should be applied when + * creating an {@linkcode Subscriber} entity. + * + * @see https://fakerjs.dev + * + * @protected + * @override + * + * @param {Faker} faker - Faker library + * @return {Pick} Subscriber entity data + */ + protected override definition( + faker: Faker + ): Pick { + return { + email: faker.internet.email().toLowerCase(), + name: faker.person.firstName() + } + } +} + +export default SubscriberFactory diff --git a/__tests__/setup/chai.ts b/__tests__/setup/chai.ts index 95ce832..6be7603 100644 --- a/__tests__/setup/chai.ts +++ b/__tests__/setup/chai.ts @@ -4,12 +4,15 @@ * @see https://chaijs.com */ +import chaiEach from 'chai-each' import chaiHttp from 'chai-http' import { chai } from 'vitest' /** * initialize chai plugins. * + * @see https://github.com/jamesthomasonjr/chai-each * @see https://github.com/chaijs/chai-http */ +chai.use(chaiEach) chai.use(chaiHttp) diff --git a/package.json b/package.json index 39288ec..84247b6 100644 --- a/package.json +++ b/package.json @@ -99,10 +99,10 @@ "@flex-development/tsconfig-utils": "1.1.2", "@flex-development/tutils": "6.0.0-alpha.10", "@graphql-eslint/eslint-plugin": "3.18.0", - "@mikro-orm/core": "5.7.2", - "@mikro-orm/mongodb": "5.7.2", + "@mikro-orm/core": "5.7.3", + "@mikro-orm/mongodb": "5.7.3", "@mikro-orm/nestjs": "5.1.8", - "@mikro-orm/seeder": "5.7.2", + "@mikro-orm/seeder": "5.7.3", "@nestjs/axios": "2.0.0", "@nestjs/common": "9.4.0", "@nestjs/config": "2.3.1", @@ -149,6 +149,7 @@ "axios": "1.3.6", "cache-manager": "5.2.0", "chai": "5.0.0-alpha.0", + "chai-each": "0.0.1", "chai-http": "4.3.0", "class-transformer": "0.5.1", "class-transformer-validator": "0.9.1", @@ -195,6 +196,7 @@ "passport-jwt": "4.0.1", "passport-local": "1.0.0", "passport-oauth2": "1.7.0", + "plur": "5.1.0", "prettier": "2.8.7", "prettier-plugin-sh": "0.12.8", "query-string": "8.1.0", @@ -219,6 +221,7 @@ }, "resolutions": { "@ardatan/sync-fetch": "larsgw/sync-fetch#head=worker_threads", + "@faker-js/faker": "8.0.0-alpha.2", "@flex-development/tutils": "6.0.0-alpha.10", "@types/chai": "4.3.4", "chai": "5.0.0-alpha.0", diff --git a/src/database/database.module.ts b/src/database/database.module.ts index 71e1aac..ef109dd 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -4,6 +4,7 @@ */ import type { IConfig } from '#src/interfaces' +import { Repository } from '#src/models' import * as pathe from '@flex-development/pathe' import { NodeEnv } from '@flex-development/tutils' import { @@ -70,12 +71,16 @@ import { template } from 'radash' debug: !TEST, discovery: { alwaysAnalyseProperties: false, + disableDynamicFileAccess: true, requireEntitiesArray: true, warnWhenNoEntities: false }, driver: MongoDriver, ensureDatabase: true, ensureIndexes: false, + entities: [], + entitiesTs: [], + entityRepository: Repository, forceEntityConstructor: true, forceUndefined: false, forceUtcTimezone: true, diff --git a/src/models/__tests__/repository.model.functional.spec.ts b/src/models/__tests__/repository.model.functional.spec.ts new file mode 100644 index 0000000..37d07c4 --- /dev/null +++ b/src/models/__tests__/repository.model.functional.spec.ts @@ -0,0 +1,84 @@ +/** + * @file Functional Tests - Repository + * @module sneusers/models/tests/functional/Repository + */ + +import Subscriber from '#fixtures/subscriber.entity' +import SubscriberFactory from '#fixtures/subscriber.factory' +import DatabaseModule from '#src/database/database.module' +import type { Spy } from '#tests/interfaces' +import createTestingModule from '#tests/utils/create-testing-module' +import { MongoEntityManager } from '@mikro-orm/mongodb' +import { MikroOrmModule } from '@mikro-orm/nestjs' +import type { TestingModule } from '@nestjs/testing' +import TestSubject from '../repository.model' + +describe('functional:models/Repository', () => { + let em: MongoEntityManager + let factory: SubscriberFactory + let ref: TestingModule + let subject: TestSubject + + beforeAll(async () => { + ref = await createTestingModule({ + imports: [DatabaseModule, MikroOrmModule.forFeature([Subscriber])] + }) + + em = ref.get(MongoEntityManager).fork() + factory = new SubscriberFactory(em) + subject = new TestSubject(em, Subscriber) + }) + + describe('#persist', () => { + let entities: Subscriber[] + let persist: Spy + let validate: Spy + + beforeAll(() => { + entities = factory.make(5) + }) + + beforeEach(() => { + persist = vi.spyOn(em, 'persist') + validate = vi.spyOn(subject, 'validate') as typeof validate + + subject.persist(entities) + }) + + it('should validate entities', () => { + expect(validate).toHaveBeenCalledTimes(entities.length) + }) + + it('should persist entities', () => { + expect(persist).toHaveBeenCalledTimes(entities.length) + }) + }) + + describe('#persistAndFlush', () => { + let flush: Spy + let persist: Spy + + beforeAll(async () => { + await em.getConnection().connect() + }) + + beforeEach(async () => { + flush = vi.spyOn(em, 'flush') + persist = vi.spyOn(subject, 'persist') as typeof persist + + await subject.persistAndFlush(factory.make(5)) + }) + + afterAll(async () => { + await em.getConnection().close(true) + }) + + it('should persist entities', async () => { + expect(persist).toHaveBeenCalledOnce() + }) + + it('should flush persisted changes', async () => { + expect(flush).toHaveBeenCalledOnce() + }) + }) +}) diff --git a/src/models/__tests__/repository.model.spec-d.ts b/src/models/__tests__/repository.model.spec-d.ts new file mode 100644 index 0000000..1e231b8 --- /dev/null +++ b/src/models/__tests__/repository.model.spec-d.ts @@ -0,0 +1,14 @@ +/** + * @file Type Tests - Repository + * @module sneusers/models/tests/unit-d/Repository + */ + +import type { IEntity } from '#src/interfaces' +import type { MongoEntityRepository } from '@mikro-orm/mongodb' +import type TestSubject from '../repository.model' + +describe('unit-d:models/Repository', () => { + it('should extend MongoEntityRepository', () => { + expectTypeOf().toMatchTypeOf>() + }) +}) diff --git a/src/models/__tests__/repository.model.spec.ts b/src/models/__tests__/repository.model.spec.ts new file mode 100644 index 0000000..9eec2b8 --- /dev/null +++ b/src/models/__tests__/repository.model.spec.ts @@ -0,0 +1,80 @@ +/** + * @file Unit Tests - Repository + * @module sneusers/models/tests/unit/Repository + */ + +import Account from '#fixtures/account.entity' +import AccountFactory from '#fixtures/account.factory' +import DatabaseModule from '#src/database/database.module' +import createTestingModule from '#tests/utils/create-testing-module' +import { ValidationError } from '@mikro-orm/core' +import { MongoEntityManager } from '@mikro-orm/mongodb' +import { MikroOrmModule } from '@mikro-orm/nestjs' +import type { TestingModule } from '@nestjs/testing' +import * as validator from 'class-validator' +import TestSubject from '../repository.model' + +describe('unit:models/Repository', () => { + let em: MongoEntityManager + let factory: AccountFactory + let ref: TestingModule + let subject: TestSubject + + beforeAll(async () => { + ref = await createTestingModule({ + imports: [DatabaseModule, MikroOrmModule.forFeature([Account])] + }) + + em = ref.get(MongoEntityManager).fork() + factory = new AccountFactory(em) + subject = new TestSubject(em, Account) + }) + + describe('#create', () => { + it('should return new entity instance', async () => { + // Arrange + const email: string = faker.internet.email() + const account: Account = new Account({ email }) + + // Act + const result = subject.create({ email }) + + // Expect + expect(result).to.be.instanceof(Account) + expect(result).to.have.property('email').equal(account.email) + }) + }) + + describe('#validate', () => { + it('should return validated entity instance', () => { + // Arrange + const account: Account = factory.makeOne() + + // Act + Expect + expect(subject.validate(account)).to.deep.equal(account) + }) + + it('should throw if entity validation fails', () => { + // Arrange + const entity: Account = new Account({ email: '' }) + let error!: ValidationError + + // Act + try { + subject.validate(entity) + } catch (e: unknown) { + error = e as typeof error + } + + // Expect + expect(error).to.be.instanceof(ValidationError) + expect(error).to.have.property('entity').deep.equal(entity) + expect(error).to.have.property('message').equal('Validation failed') + expect(error) + .to.have.property('cause') + .that.is.an('array') + .of.length(1) + .and.each.instanceof(validator.ValidationError) + }) + }) +}) diff --git a/src/models/index.ts b/src/models/index.ts index 0809064..1beb2e3 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -4,3 +4,4 @@ */ export { default as Config } from './config.model' +export { default as Repository } from './repository.model' diff --git a/src/models/repository.model.ts b/src/models/repository.model.ts new file mode 100644 index 0000000..6c8e75b --- /dev/null +++ b/src/models/repository.model.ts @@ -0,0 +1,170 @@ +/** + * @file Data Models - Repository + * @module sneusers/models/Repository + */ + +import type { IEntity } from '#src/interfaces' +import { ValidationProvider } from '#src/providers' +import type { ObjectPlain, OneOrMany } from '@flex-development/tutils' +import { ValidationError, type EntityFactory } from '@mikro-orm/core' +import { + MongoEntityRepository, + type MongoEntityManager +} from '@mikro-orm/mongodb' +import * as validator from 'class-validator' +import { get } from 'radash' + +/** + * Generic database access model. + * + * @see {@linkcode IEntity} + * @see {@linkcode MongoEntityRepository} + * + * @template T - Database entity type + * + * @class + * @extends {MongoEntityRepository} + */ +class Repository extends MongoEntityRepository { + /** + * Returns the underlying entity manager instance. + * + * @see {@linkcode MongoEntityManager} + * + * @protected + * + * @return {MongoEntityManager} Entity manager instance + */ + protected override get em(): MongoEntityManager { + return this.getEntityManager() + } + + /** + * Returns the entity factory used by the entity manager. + * + * @see {@linkcode EntityFactory} + * + * @protected + * + * @return {EntityFactory} Entity factory used by {@linkcode em} + */ + protected get factory(): EntityFactory { + return this.em.getEntityFactory() + } + + /** + * Creates a new instance of an entity and populates it with the given `data`. + * + * The entity constructor will be given parameters based on the defined + * constructor of {@linkcode T}. If the constructor parameter matches a + * property name, its value will be extracted from the given `data`. + * + * If no matching property exists, the entire `data` parameter will be passed. + * Unless there is property named `data`, this means constructors can be + * defined as `constructor(data: Partial)` and `create` will pass `data` + * into it. + * + * An explicit {@linkcode flush} operation is required to commit changes to + * the database afterwards. + * + * @template Data - Data transfer object type + * + * @public + * @override + * + * @param {Data} data - Data transfer object + * @return {T} Entity instance + */ + public override create(data: Data): T { + return this.factory.createEmbeddable(this.entityName, data, { + convertCustomTypes: true, + newEntity: true + }) + } + + /** + * Instructs the entity manager to make an instance, or set of an instances, + * managed and persistent. + * + * Entities will be entered into the database at or before transaction commit, + * or as a result of the {@linkcode flush} operation. + * + * Throws if any entity fails validation. + * + * @public + * @override + * + * @param {OneOrMany} entities - Entities to mark for persistance + * @return {MongoEntityManager} Entity manager instance + */ + public override persist(entities: OneOrMany): MongoEntityManager { + for (const entity of [entities].flat() as T[]) { + this.em.persist(this.validate(entity)) + } + + return this.em + } + + /** + * Instructs the entity manager to immediately persist an instance, or set of + * an instances, flushing all not yet persisted changes to the database too. + * + * @public + * @override + * @async + * + * @param {OneOrMany} entities - Entities to persist and flush + * @return {Promise} Nothing when complete + */ + public override async persistAndFlush(entities: OneOrMany): Promise { + await this.persist(entities).flush() + return void entities + } + + /** + * Entity validator. + * + * @public + * + * @param {T} entity - Entity instance to validate + * @return {T} Validated entity instance + * @throws {ValidationError} + */ + public validate(entity: T): T { + /** + * Validation errors. + * + * @const {validator.ValidationError[]} errors + */ + const errors: validator.ValidationError[] = validator.validateSync( + entity, + get(ValidationProvider.useValue, 'validatorOptions')! + ) + + // throw if validation errors were encountered + if (errors.length > 0) { + /** + * Entity validation error message. + * + * @const {string} message + */ + const message: string = 'Validation failed' + + /** + * Entity validation error. + * + * @const {ValidationError} error + */ + const error: ValidationError = new ValidationError(message, entity) + + // assign errors to error.cause to access errors from exception filter + error.cause = errors + + throw error + } + + return entity + } +} + +export default Repository diff --git a/yarn.lock b/yarn.lock index 64e7975..25ad456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1017,13 +1017,6 @@ __metadata: languageName: node linkType: hard -"@faker-js/faker@npm:7.6.0": - version: 7.6.0 - resolution: "@faker-js/faker@npm:7.6.0" - checksum: 8dfdb631c3c420d8352d5ff5bc97d97d8d6890c8c74d7385ae9838cb47bef5e56ce611f4052ce414a55320426ffff4135fe927f46417c44b1b3301b7fd93851a - languageName: node - linkType: hard - "@faker-js/faker@npm:8.0.0-alpha.2": version: 8.0.0-alpha.2 resolution: "@faker-js/faker@npm:8.0.0-alpha.2" @@ -1267,10 +1260,10 @@ __metadata: "@flex-development/tsconfig-utils": "npm:1.1.2" "@flex-development/tutils": "npm:6.0.0-alpha.10" "@graphql-eslint/eslint-plugin": "npm:3.18.0" - "@mikro-orm/core": "npm:5.7.2" - "@mikro-orm/mongodb": "npm:5.7.2" + "@mikro-orm/core": "npm:5.7.3" + "@mikro-orm/mongodb": "npm:5.7.3" "@mikro-orm/nestjs": "npm:5.1.8" - "@mikro-orm/seeder": "npm:5.7.2" + "@mikro-orm/seeder": "npm:5.7.3" "@nestjs/axios": "npm:2.0.0" "@nestjs/common": "npm:9.4.0" "@nestjs/config": "npm:2.3.1" @@ -1317,6 +1310,7 @@ __metadata: axios: "npm:1.3.6" cache-manager: "npm:5.2.0" chai: "npm:5.0.0-alpha.0" + chai-each: "npm:0.0.1" chai-http: "npm:4.3.0" class-transformer: "npm:0.5.1" class-transformer-validator: "npm:0.9.1" @@ -1363,6 +1357,7 @@ __metadata: passport-jwt: "npm:4.0.1" passport-local: "npm:1.0.0" passport-oauth2: "npm:1.7.0" + plur: "npm:5.1.0" prettier: "npm:2.8.7" prettier-plugin-sh: "npm:0.12.8" query-string: "npm:8.1.0" @@ -1870,16 +1865,16 @@ __metadata: languageName: node linkType: hard -"@mikro-orm/core@npm:5.7.2": - version: 5.7.2 - resolution: "@mikro-orm/core@npm:5.7.2" +"@mikro-orm/core@npm:5.7.3": + version: 5.7.3 + resolution: "@mikro-orm/core@npm:5.7.3" dependencies: acorn-loose: "npm:8.3.0" acorn-walk: "npm:8.2.0" dotenv: "npm:16.0.3" fs-extra: "npm:11.1.1" globby: "npm:11.1.0" - mikro-orm: "npm:~5.7.2" + mikro-orm: "npm:~5.7.3" reflect-metadata: "npm:0.1.13" peerDependencies: "@mikro-orm/better-sqlite": ^5.0.0 @@ -1913,13 +1908,13 @@ __metadata: optional: true "@mikro-orm/sqlite": optional: true - checksum: 1de42e4cb874962a91eac31cda4a1ff61398750ea10b3328f88048879e18bd27b3d6d04092605415e9cf41bfd8905a652473a846bd821aedddc63752e694e327 + checksum: ec03b779689bda60e1c5b4160c430c1d66481eb5b93cad8c9f63cf1f469d46febeadb47cf8faaad4336d0e6c28ccc5f12d8e5ed44e68ff4da94873275f6a7881 languageName: node linkType: hard -"@mikro-orm/mongodb@npm:5.7.2": - version: 5.7.2 - resolution: "@mikro-orm/mongodb@npm:5.7.2" +"@mikro-orm/mongodb@npm:5.7.3": + version: 5.7.3 + resolution: "@mikro-orm/mongodb@npm:5.7.3" dependencies: bson: "npm:^5.2.0" mongodb: "npm:5.3.0" @@ -1938,7 +1933,7 @@ __metadata: optional: true "@mikro-orm/seeder": optional: true - checksum: 34e654e0c8ff9452cd62b5a2e42cad076083f5d09af19a625a49093af617005c6c53774f7e571309a0724375ef72501eaa0901585cb971891e8578310cf08cbf + checksum: 1597d2f438f1139079099f4c35889f8a4151e0dbca84fe9d138626b4843cf27a65822194a6cf8ac097056704c8b1d8ef28e77851c7405c367e7835bc037cc1df languageName: node linkType: hard @@ -1953,16 +1948,16 @@ __metadata: languageName: node linkType: hard -"@mikro-orm/seeder@npm:5.7.2": - version: 5.7.2 - resolution: "@mikro-orm/seeder@npm:5.7.2" +"@mikro-orm/seeder@npm:5.7.3": + version: 5.7.3 + resolution: "@mikro-orm/seeder@npm:5.7.3" dependencies: "@faker-js/faker": "npm:7.6.0" fs-extra: "npm:11.1.1" globby: "npm:11.1.0" peerDependencies: "@mikro-orm/core": ^5.0.0 - checksum: b0db7311f0b910d5db0c43598ce1c24163a5b26fcab3187de940df66df8e33d624f02bb71c7f8a2ab0ffcc95e76450d2fed2596c206e5a3b6177e5908118e3fb + checksum: 9cc5296829c0b04c9ec138d6ab4e4e1532a02b28860f054d8aed020be1f5824e1fe4f8bb954d392c18adabefe778c7ce3d7b00ad51ce568447d58000aa78756e languageName: node linkType: hard @@ -4390,6 +4385,13 @@ __metadata: languageName: node linkType: hard +"chai-each@npm:0.0.1": + version: 0.0.1 + resolution: "chai-each@npm:0.0.1" + checksum: 545be493fb9eace3aea28ef9556a0433dd1467bd0bf96d1bb50212c0f12b833ae033fd50b56b6ca739a58868fab060ef794bca14da3b36347f4f8574d66fc2a8 + languageName: node + linkType: hard + "chai-http@npm:4.3.0": version: 4.3.0 resolution: "chai-http@npm:4.3.0" @@ -7831,6 +7833,13 @@ __metadata: languageName: node linkType: hard +"irregular-plurals@npm:^3.3.0": + version: 3.5.0 + resolution: "irregular-plurals@npm:3.5.0" + checksum: 169bbc92fb036c65e8ccd8a345599764cb90b3bd98049df7f3bdc49bd84bc649d6e610672d911acab43bccfd69952cacdc48abf95ede317f79866615d62e0197 + languageName: node + linkType: hard + "is-alphabetical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphabetical@npm:1.0.4" @@ -9124,10 +9133,10 @@ __metadata: languageName: node linkType: hard -"mikro-orm@npm:~5.7.2": - version: 5.7.2 - resolution: "mikro-orm@npm:5.7.2" - checksum: 5081e037c063bf8e665b3b384a1930b1436e5ac0873b409d6ec63b945a370c278659a59d35d1d15de60b093471607d1487dcd1e07f13b8fbba37231ad6a66c64 +"mikro-orm@npm:~5.7.3": + version: 5.7.3 + resolution: "mikro-orm@npm:5.7.3" + checksum: 3c8a36b9deba0a5f340dd16f05a3fa528bcd5a2a8ae350504c7dfff14839810ffacc8cdb168f65a4a355fa0cf8af40cfffa1b8582b13c077b11fc13f78afc6c7 languageName: node linkType: hard @@ -10372,6 +10381,15 @@ __metadata: languageName: node linkType: hard +"plur@npm:5.1.0": + version: 5.1.0 + resolution: "plur@npm:5.1.0" + dependencies: + irregular-plurals: "npm:^3.3.0" + checksum: d10f83a5d3b37ade27d0230de0e4aa61968ffb48cae5da5cfc6ba3f11a262cda60bbd58433cf1884553cf3d69cc82abaae999bbfc4b99667caca2732f08acdcd + languageName: node + linkType: hard + "pluralize@npm:^8.0.0": version: 8.0.0 resolution: "pluralize@npm:8.0.0"