From e715c035e57c206a08474a226a3f6a927503e63d Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Wed, 6 Jan 2021 15:20:24 -0600 Subject: [PATCH] Release v6.0.0 * Update npms * Change `.destroy()` to not return records by default. Use `.destroy({}, { returnRecords: true })` for previous behavior * Return `void` instead of `boolean` when not returning records --- CHANGELOG.md | 5 + package.json | 26 +- src/IRepository.ts | 54 ++-- src/Repository.ts | 81 +++--- ...eleteOptions.ts => CreateUpdateOptions.ts} | 2 +- src/query/DeleteOptions.ts | 11 + src/query/index.ts | 3 +- tests/repository.tests.ts | 232 ++++++++++++++++-- 8 files changed, 302 insertions(+), 112 deletions(-) rename src/query/{CreateUpdateDeleteOptions.ts => CreateUpdateOptions.ts} (60%) create mode 100644 src/query/DeleteOptions.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c10d417..dfc30e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### 6.0.0 + * Update npms + * Change `.destroy()` to not return records by default. Use `.destroy({}, { returnRecords: true })` for previous behavior + * Return `void` instead of `boolean` when not returning records + ### 5.0.3 * Update npms diff --git a/package.json b/package.json index d3c8818..8aeafd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "5.0.3", + "version": "6.0.0", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", @@ -19,8 +19,8 @@ "node": ">=10" }, "dependencies": { - "@types/lodash": "^4.14.165", - "@types/node": "^14.14.10", + "@types/lodash": "^4.14.167", + "@types/node": "^14.14.20", "@types/pg": "^7.14.7", "lodash": "4.17.20", "pg": "8.5.1", @@ -29,30 +29,30 @@ "devDependencies": { "@types/chai": "^4.2.14", "@types/faker": "^5.1.5", - "@types/mocha": "^8.0.4", - "@typescript-eslint/eslint-plugin": "^4.9.0", - "@typescript-eslint/parser": "^4.9.0", + "@types/mocha": "^8.2.0", + "@typescript-eslint/eslint-plugin": "^4.12.0", + "@typescript-eslint/parser": "^4.12.0", "chai": "^4.2.0", - "eslint": "^7.14.0", + "eslint": "^7.17.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-typescript": "^12.0.0", - "eslint-config-prettier": "^6.15.0", + "eslint-config-prettier": "^7.1.0", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsdoc": "^30.7.8", + "eslint-plugin-jsdoc": "^30.7.13", "eslint-plugin-mocha": "^8.0.0", - "eslint-plugin-prettier": "^3.2.0", + "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-security": "^1.4.0", "faker": "^5.1.0", - "husky": "^5.0.4", + "husky": "^5.0.6", "lint-staged": "^10.5.3", "mocha": "^8.2.1", "pinst": "^2.1.1", "prettier": "^2.2.1", "strict-event-emitter-types": "^2.0.0", "ts-mockito": "^2.6.1", - "ts-node": "^9.1.0", - "typescript": "^4.1.2" + "ts-node": "^9.1.1", + "typescript": "^4.1.3" }, "scripts": { "build": "tsc", diff --git a/src/IRepository.ts b/src/IRepository.ts index fc21b9b..8c5eb80 100644 --- a/src/IRepository.ts +++ b/src/IRepository.ts @@ -1,10 +1,12 @@ import type { Entity } from './Entity'; import type { IReadonlyRepository } from './IReadonlyRepository'; import type { - CreateUpdateDeleteOptions, // + CreateUpdateOptions, // + DeleteOptions, DestroyResult, DoNotReturnRecords, WhereQuery, + ReturnSelect, } from './query'; export interface IRepository extends IReadonlyRepository { @@ -12,17 +14,17 @@ export interface IRepository extends IReadonlyRepository { * Creates a objects using the specified values * @param {object} values - Values to insert as multiple new objects. * @param {{returnRecords: false}} options - * @returns {boolean} + * @returns {object} */ - create(values: Partial, options?: CreateUpdateDeleteOptions): Promise; + create(values: Partial, options?: ReturnSelect): Promise; /** * Creates a objects using the specified values * @param {object|object[]} values - Values to insert as multiple new objects. * @param {{returnRecords: false}} options - * @returns {boolean} + * @returns {void} */ - create(values: Partial | Partial[], options: DoNotReturnRecords): Promise; + create(values: Partial | Partial[], options: DoNotReturnRecords): Promise; /** * Creates a objects using the specified values @@ -30,9 +32,9 @@ export interface IRepository extends IReadonlyRepository { * @param {object} [options] * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {boolean} + * @returns {object[]} */ - create(values: Partial[], options?: CreateUpdateDeleteOptions): Promise; + create(values: Partial[], options?: ReturnSelect): Promise; /** * Creates an object using the specified values @@ -40,18 +42,18 @@ export interface IRepository extends IReadonlyRepository { * @param {object} [options] * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {object} Return value from the db + * @returns {object|object[]|void} Return value from the db */ - create(values: Partial | Partial[], options?: CreateUpdateDeleteOptions): Promise; + create(values: Partial | Partial[], options?: CreateUpdateOptions): Promise; /** * Updates object(s) matching the where query, with the specified values * @param {object} where - Object representing the where query * @param {object} values - Values to update * @param {{returnRecords: false}} options - * @returns {boolean} + * @returns {void} */ - update(where: WhereQuery, values: Partial, options: DoNotReturnRecords): Promise; + update(where: WhereQuery, values: Partial, options: DoNotReturnRecords): Promise; /** * Updates object(s) matching the where query, with the specified values @@ -60,9 +62,9 @@ export interface IRepository extends IReadonlyRepository { * @param {object} [options] - Values to update * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {boolean} + * @returns {object[]} */ - update(where: WhereQuery, values: Partial, options?: CreateUpdateDeleteOptions): Promise; + update(where: WhereQuery, values: Partial, options?: ReturnSelect): Promise; /** * Updates object(s) matching the where query, with the specified values @@ -71,34 +73,34 @@ export interface IRepository extends IReadonlyRepository { * @param {object} [options] * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {object[]} Return values from the db or `true` if returnRecords=false + * @returns {object[]|void} Return values from the db or `true` if returnRecords=false */ - update(where: WhereQuery, values: Partial, options?: CreateUpdateDeleteOptions): Promise; + update(where: WhereQuery, values: Partial, options?: CreateUpdateOptions): Promise; /** * Destroys object(s) matching the where query - * @param {object} where - Object representing the where query - * @param {{returnRecords: false}} options - * @returns {boolean} + * @param {object} [where] - Object representing the where query + * @returns {void} */ - destroy(where: WhereQuery, options: DoNotReturnRecords): DestroyResult; + destroy(where?: WhereQuery): DestroyResult; /** * Destroys object(s) matching the where query * @param {object} where - Object representing the where query - * @param {object} [options] - Determines if inserted records should be returned - * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned - * @returns {boolean} + * @param {object} options - Determines if inserted records should be returned + * @param {boolean} [options.returnRecords] - Determines if inserted records should be returned + * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. + * @returns {object[]} */ - destroy(where?: WhereQuery, options?: CreateUpdateDeleteOptions): DestroyResult; + destroy(where: WhereQuery, options: DeleteOptions): DestroyResult; /** * Destroys object(s) matching the where query * @param {object} where - Object representing the where query * @param {object} [options] - * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned + * @param {boolean} [options.returnRecords=false] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {object[]|boolean} Records affected or `true` if returnRecords=false + * @returns {object[]|void} `void` or records affected if returnRecords=true */ - destroy(where: WhereQuery, options?: CreateUpdateDeleteOptions): DestroyResult; + destroy(where: WhereQuery, options?: TOptions): DestroyResult; } diff --git a/src/Repository.ts b/src/Repository.ts index 975de81..20b3f33 100644 --- a/src/Repository.ts +++ b/src/Repository.ts @@ -3,12 +3,13 @@ import _ from 'lodash'; import type { Entity } from './Entity'; import type { IRepository } from './IRepository'; import type { - CreateUpdateDeleteOptions, // + CreateUpdateOptions, // DestroyResult, DoNotReturnRecords, ReturnSelect, WhereQuery, } from './query'; +import type { DeleteOptions } from './query/DeleteOptions'; import { ReadonlyRepository } from './ReadonlyRepository'; import { getDeleteQueryAndParams, getInsertQueryAndParams, getUpdateQueryAndParams } from './SqlHelper'; @@ -17,17 +18,17 @@ export class Repository extends ReadonlyRepository implemen * Creates a objects using the specified values * @param {object} values - Values to insert as multiple new objects. * @param {{returnRecords: false}} options - * @returns {boolean} + * @returns {object} */ - public async create(values: Partial, options?: CreateUpdateDeleteOptions): Promise; + public create(values: Partial, options?: ReturnSelect): Promise; /** * Creates a objects using the specified values * @param {object|object[]} values - Values to insert as multiple new objects. * @param {{returnRecords: false}} options - * @returns {boolean} + * @returns {void} */ - public async create(values: Partial | Partial[], options: DoNotReturnRecords): Promise; + public create(values: Partial | Partial[], options: DoNotReturnRecords): Promise; /** * Creates a objects using the specified values @@ -35,9 +36,9 @@ export class Repository extends ReadonlyRepository implemen * @param {object} [options] * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {boolean} + * @returns {object[]} */ - public async create(values: Partial[], options?: CreateUpdateDeleteOptions): Promise; + public create(values: Partial[], options?: ReturnSelect): Promise; /** * Creates an object using the specified values @@ -45,9 +46,9 @@ export class Repository extends ReadonlyRepository implemen * @param {object} [options] * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {object} Return value from the db + * @returns {object|object[]|void} Return value from the db */ - public async create(values: Partial | Partial[], options?: CreateUpdateDeleteOptions): Promise { + public async create(values: Partial | Partial[], options?: CreateUpdateOptions): Promise { if (this.model.readonly) { throw new Error(`${this.model.name} is readonly.`); } @@ -97,7 +98,7 @@ export class Repository extends ReadonlyRepository implemen throw new Error('Unknown error getting created rows back from the database'); } - return true; + return undefined; } /** @@ -105,9 +106,9 @@ export class Repository extends ReadonlyRepository implemen * @param {object} where - Object representing the where query * @param {object} values - Values to update * @param {{returnRecords: false}} options - * @returns {boolean} + * @returns {void} */ - public async update(where: WhereQuery, values: Partial, options: DoNotReturnRecords): Promise; + public update(where: WhereQuery, values: Partial, options: DoNotReturnRecords): Promise; /** * Updates object(s) matching the where query, with the specified values @@ -116,9 +117,9 @@ export class Repository extends ReadonlyRepository implemen * @param {object} [options] - Values to update * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {boolean} + * @returns {object[]} */ - public async update(where: WhereQuery, values: Partial, options?: CreateUpdateDeleteOptions): Promise; + public update(where: WhereQuery, values: Partial, options?: ReturnSelect): Promise; /** * Updates object(s) matching the where query, with the specified values @@ -127,9 +128,9 @@ export class Repository extends ReadonlyRepository implemen * @param {object} [options] * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {object[]} Return values from the db or `true` if returnRecords=false + * @returns {object[]|void} Return values from the db or `true` if returnRecords=false */ - public async update(where: WhereQuery, values: Partial, options?: CreateUpdateDeleteOptions): Promise { + public async update(where: WhereQuery, values: Partial, options?: CreateUpdateOptions): Promise { if (this.model.readonly) { throw new Error(`${this.model.name} is readonly.`); } @@ -168,35 +169,35 @@ export class Repository extends ReadonlyRepository implemen return this._buildInstances(results.rows); } - return true; + return undefined; } /** * Destroys object(s) matching the where query - * @param {object} where - Object representing the where query - * @param {{returnRecords: false}} options - * @returns {boolean} + * @param {object} [where] - Object representing the where query + * @returns {void} */ - public destroy(where: WhereQuery, options: DoNotReturnRecords): DestroyResult; + public destroy(where?: WhereQuery): DestroyResult; /** * Destroys object(s) matching the where query * @param {object} where - Object representing the where query - * @param {object} [options] - Determines if inserted records should be returned - * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned - * @returns {boolean} + * @param {object} options - Determines if inserted records should be returned + * @param {boolean} [options.returnRecords] - Determines if inserted records should be returned + * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. + * @returns {object[]} */ - public destroy(where?: WhereQuery, options?: CreateUpdateDeleteOptions): DestroyResult; + public destroy(where: WhereQuery, options: DeleteOptions): DestroyResult; /** * Destroys object(s) matching the where query * @param {object} where - Object representing the where query * @param {object} [options] - * @param {boolean} [options.returnRecords=true] - Determines if inserted records should be returned + * @param {boolean} [options.returnRecords=false] - Determines if inserted records should be returned * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. - * @returns {object[]|boolean} Records affected or `true` if returnRecords=false + * @returns {object[]|void} `void` or records affected if returnRecords=true */ - public destroy(where: WhereQuery = {}, options?: CreateUpdateDeleteOptions): DestroyResult { + public destroy(where: WhereQuery = {}, options?: DeleteOptions): DestroyResult { if (this.model.readonly) { throw new Error(`${this.model.name} is readonly.`); } @@ -205,32 +206,23 @@ export class Repository extends ReadonlyRepository implemen // eslint-disable-next-line @typescript-eslint/no-this-alias const modelInstance = this; - - let returnRecords = true; - let returnSelect: string[] | undefined; - if (options) { - if ((options as DoNotReturnRecords).returnRecords === false) { - returnRecords = false; - } else if ((options as ReturnSelect).returnSelect) { - returnSelect = (options as ReturnSelect).returnSelect; - } - } + const returnSelect = options?.returnSelect; + const returnRecords = options?.returnRecords || !!returnSelect; return { /** * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery): DestroyResult { + where(value: WhereQuery): DestroyResult { // eslint-disable-next-line no-param-reassign where = value; return this; }, - async then(resolve: (result: T[] | boolean) => T[] | boolean, reject: (err: Error) => void): Promise { + async then(resolve: (result: T[] | void) => T[] | void, reject: (err: Error) => void): Promise { if (_.isString(where)) { - reject(new Error('The query cannot be a string, it must be an object')); - return false; + return reject(new Error('The query cannot be a string, it must be an object')); } try { @@ -248,7 +240,7 @@ export class Repository extends ReadonlyRepository implemen return resolve(modelInstance._buildInstances(result.rows)); } - return resolve(true); + return resolve(); } catch (ex) { const typedException = ex as Error; if (typedException.stack) { @@ -257,8 +249,7 @@ export class Repository extends ReadonlyRepository implemen typedException.stack = stack; } - reject(typedException); - return false; + return reject(typedException); } }, }; diff --git a/src/query/CreateUpdateDeleteOptions.ts b/src/query/CreateUpdateOptions.ts similarity index 60% rename from src/query/CreateUpdateDeleteOptions.ts rename to src/query/CreateUpdateOptions.ts index 2af5ce8..2b44548 100644 --- a/src/query/CreateUpdateDeleteOptions.ts +++ b/src/query/CreateUpdateOptions.ts @@ -1,4 +1,4 @@ import type { DoNotReturnRecords } from './DoNotReturnRecords'; import type { ReturnSelect } from './ReturnSelect'; -export type CreateUpdateDeleteOptions = DoNotReturnRecords | ReturnSelect; +export type CreateUpdateOptions = DoNotReturnRecords | ReturnSelect; diff --git a/src/query/DeleteOptions.ts b/src/query/DeleteOptions.ts new file mode 100644 index 0000000..d31da8f --- /dev/null +++ b/src/query/DeleteOptions.ts @@ -0,0 +1,11 @@ +interface ReturnSelect { + returnSelect: string[]; + returnRecords?: true; +} + +interface ReturnRecords { + returnRecords: true; + returnSelect?: string[]; +} + +export type DeleteOptions = ReturnSelect | ReturnRecords; diff --git a/src/query/index.ts b/src/query/index.ts index 7f826a7..dc774e2 100644 --- a/src/query/index.ts +++ b/src/query/index.ts @@ -1,6 +1,7 @@ export * from './Comparer'; export * from './CountResult'; -export * from './CreateUpdateDeleteOptions'; +export * from './CreateUpdateOptions'; +export * from './DeleteOptions'; export * from './DestroyResult'; export * from './DoNotReturnRecords'; export * from './FindArgs'; diff --git a/tests/repository.tests.ts b/tests/repository.tests.ts index 7a080b2..b8fe65c 100644 --- a/tests/repository.tests.ts +++ b/tests/repository.tests.ts @@ -118,7 +118,7 @@ describe('Repository', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([product.name, [], product.store]); }); - it('should return true if single value is specified and returnRecords=false', async () => { + it('should return void if single value is specified and returnRecords=false', async () => { const product = { id: faker.random.uuid(), name: `product - ${faker.random.uuid()}`, @@ -138,8 +138,7 @@ describe('Repository', () => { ); verify(mockedPool.query(anyString(), anything())).once(); - should.exist(result); - result.should.equal(true); + should.not.exist(result); const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3)'); @@ -186,7 +185,7 @@ describe('Repository', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([products[0].name, products[1].name, [], [], products[0].store, products[1].store]); }); - it('should return true if multiple values are specified and returnRecords=false', async () => { + it('should return void if multiple values are specified and returnRecords=false', async () => { const products = [ { id: faker.random.uuid(), @@ -213,7 +212,7 @@ describe('Repository', () => { ); verify(mockedPool.query(anyString(), anything())).once(); - result.should.equal(true); + should.not.exist(result); const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$3,$5),($2,$4,$6)'); @@ -304,7 +303,7 @@ describe('Repository', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([product.name, product.store, product.id]); }); - it('should return true if returnRecords=false', async () => { + it('should return void if returnRecords=false', async () => { const product = { id: faker.random.uuid(), name: `product - ${faker.random.uuid()}`, @@ -327,7 +326,7 @@ describe('Repository', () => { ); verify(mockedPool.query(anyString(), anything())).once(); - result.should.equal(true); + should.not.exist(result); const [query, params] = capture(mockedPool.query).first(); query.should.equal('UPDATE "products" SET "name"=$1,"store_id"=$2 WHERE "id"=$3'); @@ -336,7 +335,7 @@ describe('Repository', () => { }); }); describe('#destroy()', () => { - it('should support call without constraints', async () => { + it('should delete all records and return void if there are no constraints', async () => { const products = [ { id: faker.random.uuid(), @@ -351,6 +350,28 @@ describe('Repository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.destroy(); + should.not.exist(result); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('DELETE FROM "products"'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + params!.should.deep.equal([]); + }); + it('should delete all records if empty constraint and return all data if returnRecords=true', async () => { + const products = [ + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + ]; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + + const result = await ProductRepository.destroy({}, { returnRecords: true }); should.exist(result); result.should.deep.equal(products); @@ -359,6 +380,52 @@ describe('Repository', () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); + it('should delete all records if empty constraint and return specific columns if returnSelect is specified', async () => { + const products = [ + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + ]; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + + const result = await ProductRepository.destroy({}, { returnSelect: ['name'] }); + should.exist(result); + result.should.deep.equal(products); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('DELETE FROM "products" RETURNING "name","id"'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + params!.should.deep.equal([]); + }); + it('should delete all records if empty constraint and return id column if returnSelect is empty', async () => { + const products = [ + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + ]; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + + const result = await ProductRepository.destroy({}, { returnSelect: [] }); + should.exist(result); + result.should.deep.equal(products); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('DELETE FROM "products" RETURNING "id"'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + params!.should.deep.equal([]); + }); it('should support call without constraints (non "id" primaryKey)', async () => { const model = new ModelMetadata({ name: 'foo', @@ -404,6 +471,58 @@ describe('Repository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await repository.destroy(); + should.not.exist(result); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('DELETE FROM "foo"'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + params!.should.deep.equal([]); + }); + it('should support call without constraints (non "id" primaryKey) if returnRecords=true', async () => { + const model = new ModelMetadata({ + name: 'foo', + type: TestEntity, + }); + model.columns = [ + new ColumnTypeMetadata({ + target: 'foo', + name: 'foobario', + propertyName: 'foobario', + primary: true, + type: 'integer', + }), + new ColumnTypeMetadata({ + target: 'foo', + name: 'name', + propertyName: 'name', + required: true, + defaultsTo: 'foobar', + type: 'string', + }), + ]; + const repositories: RepositoriesByModelNameLowered = {}; + const repository = new Repository({ + modelMetadata: model, + type: model.type, + pool: instance(mockedPool), + repositoriesByModelNameLowered: repositories, + }); + repositories[model.name.toLowerCase()] = repository; + + const products = [ + { + foobario: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + { + foobario: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + ]; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + + const result = await repository.destroy({}, { returnRecords: true }); should.exist(result); result.should.deep.equal(products); @@ -434,6 +553,38 @@ describe('Repository', () => { id: _.map(products, 'id'), store, }); + should.not.exist(result); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('DELETE FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + params!.should.deep.equal([_.map(products, 'id'), store.id]); + }); + it('should support call constraints as a parameter if returnRecords=true', async () => { + const store = { + id: faker.random.uuid(), + name: `store - ${faker.random.uuid()}`, + }; + const products = [ + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + ]; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + + const result = await ProductRepository.destroy( + { + id: _.map(products, 'id'), + store, + }, + { returnRecords: true }, + ); should.exist(result); result.should.deep.equal(products); @@ -462,6 +613,33 @@ describe('Repository', () => { const result = await ProductRepository.destroy().where({ store: store.id, }); + should.not.exist(result); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('DELETE FROM "products" WHERE "store_id"=$1'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + params!.should.deep.equal([store.id]); + }); + it('should support call with chained where constraints if returnRecords=true', async () => { + const store = { + id: faker.random.uuid(), + name: `store - ${faker.random.uuid()}`, + }; + const products = [ + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + { + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, + }, + ]; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + const result = await ProductRepository.destroy({}, { returnRecords: true }).where({ + store: store.id, + }); should.exist(result); result.should.deep.equal(products); @@ -492,40 +670,42 @@ describe('Repository', () => { store: store.id, }), ]); - should.exist(result); - result.should.deep.equal(products); + should.not.exist(result); const [query, params] = capture(mockedPool.query).first(); - query.should.equal('DELETE FROM "products" WHERE "store_id"=$1 RETURNING "id","name","sku","alias_names" AS "aliases","store_id" AS "store"'); + query.should.equal('DELETE FROM "products" WHERE "store_id"=$1'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([store.id]); }); - it('should return true if returnRecords=false', async () => { - const product = { + it('should support call with chained where constraints if returnRecords=true - Promise.all', async () => { + const store = { id: faker.random.uuid(), - name: `product - ${faker.random.uuid()}`, - store: faker.random.number(), + name: `store - ${faker.random.uuid()}`, }; - - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); - - const result = await ProductRepository.destroy( + const products = [ { - id: product.id, + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, }, { - returnRecords: false, + id: faker.random.uuid(), + name: `product - ${faker.random.uuid()}`, }, - ); + ]; - verify(mockedPool.query(anyString(), anything())).once(); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); + const [result] = await Promise.all([ + ProductRepository.destroy({}, { returnRecords: true }).where({ + store: store.id, + }), + ]); should.exist(result); - result.should.equal(true); + result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); - query.should.equal('DELETE FROM "products" WHERE "id"=$1'); + query.should.equal('DELETE FROM "products" WHERE "store_id"=$1 RETURNING "id","name","sku","alias_names" AS "aliases","store_id" AS "store"'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal([product.id]); + params!.should.deep.equal([store.id]); }); }); });