From 59c2f9453a69eb6cdc184ef139e1a02b7f4366b5 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 28 Dec 2021 12:00:05 -0600 Subject: [PATCH] Release 9.2.4 * Fix PromiseLike signatures. Remove ChainablePromiseLike --- CHANGELOG.md | 4 ++ package-lock.json | 2 +- package.json | 2 +- src/ChainablePromiseLike.ts | 4 -- src/ReadonlyRepository.ts | 36 +++++----- src/Repository.ts | 9 ++- src/query/CountResult.ts | 3 +- src/query/DestroyResult.ts | 3 +- src/query/FindOneResult.ts | 3 +- src/query/FindResult.ts | 3 +- tests/models/LevelOne.ts | 29 ++++++++ tests/models/LevelThree.ts | 20 ++++++ tests/models/LevelTwo.ts | 29 ++++++++ tests/models/index.ts | 3 + tests/readonlyRepository.tests.ts | 108 +++++++++++++++++++++++++++++- 15 files changed, 222 insertions(+), 36 deletions(-) delete mode 100644 src/ChainablePromiseLike.ts create mode 100644 tests/models/LevelOne.ts create mode 100644 tests/models/LevelThree.ts create mode 100644 tests/models/LevelTwo.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f112ea..cb8a353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 9.2.4 - 2021-12-28 + +- Fix PromiseLike signatures. Remove ChainablePromiseLike + ## 9.2.3 - 2021-12-27 - Update npms diff --git a/package-lock.json b/package-lock.json index ca4ded4..cd905a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "9.2.3", + "version": "9.2.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0783671..8858a89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "9.2.3", + "version": "9.2.4", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", diff --git a/src/ChainablePromiseLike.ts b/src/ChainablePromiseLike.ts deleted file mode 100644 index 0f1280f..0000000 --- a/src/ChainablePromiseLike.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ChainablePromiseLike { - then(resolve: (value: TResult) => PromiseLike | TResult, reject: (error: Error) => PromiseLike | TRejectResult): Promise; - then(resolve: (value: TResult) => PromiseLike | TResult, reject: (error: Error) => PromiseLike | void): Promise; -} diff --git a/src/ReadonlyRepository.ts b/src/ReadonlyRepository.ts index d7632f6..d73afb5 100644 --- a/src/ReadonlyRepository.ts +++ b/src/ReadonlyRepository.ts @@ -196,10 +196,13 @@ export class ReadonlyRepository implements IReadonlyRepository return this as FindOneResult, TProperty> & PickAsType>; }, - async then(resolve: (result: QueryResult | null) => Promise> | QueryResult | null, reject: (err: Error) => void): Promise | null> { + async then | null, TErrorResult = void>( + resolve: (result: QueryResult | null) => PromiseLike | TResult, + reject: (error: Error) => PromiseLike | TErrorResult, + ): Promise { try { if (_.isString(where)) { - throw new Error('The query cannot be a string, it must be an object'); + return await reject(new Error('The query cannot be a string, it must be an object')); } const { query, params } = getSelectQueryAndParams({ @@ -239,9 +242,7 @@ export class ReadonlyRepository implements IReadonlyRepository typedException.stack = stack; } - reject(typedException); - - return null; + return reject(typedException); } }, }; @@ -401,11 +402,13 @@ export class ReadonlyRepository implements IReadonlyRepository const safePage = Math.max(page, 1); return this.skip(safePage * paginateLimit - paginateLimit).limit(paginateLimit); }, - async then(resolve: (result: QueryResult[]) => QueryResult[], reject: (err: Error) => void): Promise[]> { + async then[], TErrorResult = void>( + resolve: (result: QueryResult[]) => PromiseLike | TResult, + reject: (error: Error) => PromiseLike | TErrorResult, + ): Promise { try { if (_.isString(where)) { - reject(new Error('The query cannot be a string, it must be an object')); - return []; + return await reject(new Error('The query cannot be a string, it must be an object')); } const { query, params } = getSelectQueryAndParams({ @@ -426,7 +429,7 @@ export class ReadonlyRepository implements IReadonlyRepository await modelInstance.populateFields(entities, populates); } - return resolve(entities); + return await resolve(entities); } catch (ex) { const typedException = ex as Error; if (typedException.stack) { @@ -435,8 +438,7 @@ export class ReadonlyRepository implements IReadonlyRepository typedException.stack = stack; } - reject(typedException); - return []; + return reject(typedException); } }, }; @@ -464,7 +466,10 @@ export class ReadonlyRepository implements IReadonlyRepository return this; }, - async then(resolve: (result: number) => number, reject: (err: Error) => void): Promise { + async then( + resolve: (result: number) => PromiseLike | TResult, + reject: (error: Error) => PromiseLike | TErrorResult, + ): Promise { try { const { query, params } = getCountQueryAndParams({ repositoriesByModelNameLowered: modelInstance._repositoriesByModelNameLowered, @@ -475,7 +480,7 @@ export class ReadonlyRepository implements IReadonlyRepository const result = await modelInstance._pool.query<{ count: string }>(query, params); const originalValue = result.rows[0].count; - return resolve(Number(originalValue)); + return await resolve(Number(originalValue)); } catch (ex) { const typedException = ex as Error; if (typedException.stack) { @@ -484,8 +489,7 @@ export class ReadonlyRepository implements IReadonlyRepository typedException.stack = stack; } - reject(typedException); - return 0; + return reject(typedException); } }, }; @@ -540,7 +544,7 @@ export class ReadonlyRepository implements IReadonlyRepository protected _buildInstances(rows: Partial>[]): QueryResult[] { if (_.isNil(rows)) { - return rows; + return []; } return rows.map((row: Partial>) => this._buildInstance(row)); diff --git a/src/Repository.ts b/src/Repository.ts index a919390..c039fe2 100644 --- a/src/Repository.ts +++ b/src/Repository.ts @@ -222,7 +222,10 @@ export class Repository extends ReadonlyRepository implemen return this; }, - async then(resolve: (result: QueryResult[] | void) => QueryResult[] | void, reject: (err: Error) => void): Promise[] | void> { + async then[], TErrorResult = void>( + resolve: (result: QueryResult[] | void) => PromiseLike | TResult, + reject: (error: Error) => PromiseLike | TErrorResult, + ): Promise { if (_.isString(where)) { return reject(new Error('The query cannot be a string, it must be an object')); } @@ -239,10 +242,10 @@ export class Repository extends ReadonlyRepository implemen const result = await modelInstance._pool.query>>(query, params); if (returnRecords) { - return resolve(modelInstance._buildInstances(result.rows)); + return await resolve(modelInstance._buildInstances(result.rows)); } - return resolve(); + return await resolve(); } catch (ex) { const typedException = ex as Error; if (typedException.stack) { diff --git a/src/query/CountResult.ts b/src/query/CountResult.ts index 88c0bb3..4eb3285 100644 --- a/src/query/CountResult.ts +++ b/src/query/CountResult.ts @@ -1,8 +1,7 @@ -import type { ChainablePromiseLike } from '../ChainablePromiseLike'; import type { Entity } from '../Entity'; import type { WhereQuery } from './WhereQuery'; -export interface CountResult extends ChainablePromiseLike { +export interface CountResult extends PromiseLike { where(args: WhereQuery): CountResult | number; } diff --git a/src/query/DestroyResult.ts b/src/query/DestroyResult.ts index 7f5f7c4..b202a70 100644 --- a/src/query/DestroyResult.ts +++ b/src/query/DestroyResult.ts @@ -1,8 +1,7 @@ -import type { ChainablePromiseLike } from '../ChainablePromiseLike'; import type { Entity } from '../Entity'; import type { WhereQuery } from './WhereQuery'; -export interface DestroyResult extends ChainablePromiseLike { +export interface DestroyResult extends PromiseLike { where(args: WhereQuery): DestroyResult; } diff --git a/src/query/FindOneResult.ts b/src/query/FindOneResult.ts index a064c09..3bed372 100644 --- a/src/query/FindOneResult.ts +++ b/src/query/FindOneResult.ts @@ -1,4 +1,3 @@ -import type { ChainablePromiseLike } from '../ChainablePromiseLike'; import type { Entity } from '../Entity'; import type { GetValueType, PickAsPopulated, PickByValueType, PickAsType, QueryResult } from '../types'; @@ -6,7 +5,7 @@ import type { PopulateArgs } from './PopulateArgs'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface FindOneResult extends ChainablePromiseLike { +export interface FindOneResult extends PromiseLike { where(args: WhereQuery): FindOneResult; populate & keyof T>( propertyName: TProperty, diff --git a/src/query/FindResult.ts b/src/query/FindResult.ts index e09d7e8..b6153e0 100644 --- a/src/query/FindResult.ts +++ b/src/query/FindResult.ts @@ -1,4 +1,3 @@ -import type { ChainablePromiseLike } from '../ChainablePromiseLike'; import type { Entity } from '../Entity'; import type { PickByValueType, GetValueType, PickAsPopulated } from '../types'; @@ -7,7 +6,7 @@ import type { PopulateArgs } from './PopulateArgs'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface FindResult extends ChainablePromiseLike { +export interface FindResult extends PromiseLike { where(args: WhereQuery): FindResult; populate & keyof T>( propertyName: TProperty, diff --git a/tests/models/LevelOne.ts b/tests/models/LevelOne.ts new file mode 100644 index 0000000..f877bd9 --- /dev/null +++ b/tests/models/LevelOne.ts @@ -0,0 +1,29 @@ +import { column, primaryColumn, table, Entity } from '../../src'; + +import type { LevelTwo } from './LevelTwo'; + +@table({ + name: 'level_one', +}) +export class LevelOne extends Entity { + @primaryColumn({ type: 'string' }) + public id!: string; + + @column({ + type: 'string', + required: true, + }) + public one!: string; + + @column({ + type: 'string', + }) + public foo?: string; + + @column({ + required: true, + model: 'LevelTwo', + name: 'level_two_id', + }) + public levelTwo!: LevelTwo | string; +} diff --git a/tests/models/LevelThree.ts b/tests/models/LevelThree.ts new file mode 100644 index 0000000..9bc5e70 --- /dev/null +++ b/tests/models/LevelThree.ts @@ -0,0 +1,20 @@ +import { column, primaryColumn, table, Entity } from '../../src'; + +@table({ + name: 'level_three', +}) +export class LevelThree extends Entity { + @primaryColumn({ type: 'string' }) + public id!: string; + + @column({ + type: 'string', + }) + public foo?: string; + + @column({ + type: 'string', + required: true, + }) + public three!: string; +} diff --git a/tests/models/LevelTwo.ts b/tests/models/LevelTwo.ts new file mode 100644 index 0000000..e963a0b --- /dev/null +++ b/tests/models/LevelTwo.ts @@ -0,0 +1,29 @@ +import { column, primaryColumn, table, Entity } from '../../src'; + +import type { LevelThree } from './LevelThree'; + +@table({ + name: 'level_two', +}) +export class LevelTwo extends Entity { + @primaryColumn({ type: 'string' }) + public id!: string; + + @column({ + type: 'string', + required: true, + }) + public two!: string; + + @column({ + type: 'string', + }) + public foo?: string; + + @column({ + required: true, + model: 'LevelThree', + name: 'level_three_id', + }) + public levelThree!: LevelThree | string; +} diff --git a/tests/models/index.ts b/tests/models/index.ts index 434d3fb..3b31f41 100644 --- a/tests/models/index.ts +++ b/tests/models/index.ts @@ -1,6 +1,9 @@ export * from './Category'; export * from './Classroom'; export * from './KitchenSink'; +export * from './LevelOne'; +export * from './LevelTwo'; +export * from './LevelThree'; export * from './ParkingSpace'; export * from './Product'; export * from './ProductCategory'; diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index 300e358..5b011b3 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -15,6 +15,9 @@ import { Category, Classroom, KitchenSink, + LevelOne, + LevelThree, + LevelTwo, ParkingSpace, Product, ProductCategory, @@ -44,6 +47,9 @@ describe('ReadonlyRepository', () => { let should: Chai.Should; const mockedPool: Pool = mock(Pool); /* eslint-disable @typescript-eslint/naming-convention */ + let LevelOneRepository: Repository; + let LevelTwoRepository: Repository; + let LevelThreeRepository: Repository; let ProductRepository: Repository; let ReadonlyProductRepository: ReadonlyRepository; let ReadonlyKitchenSinkRepository: ReadonlyRepository; @@ -64,6 +70,9 @@ describe('ReadonlyRepository', () => { Classroom, // Category, KitchenSink, + LevelOne, + LevelTwo, + LevelThree, ParkingSpace, Product, ProductCategory, @@ -80,6 +89,9 @@ describe('ReadonlyRepository', () => { pool: instance(mockedPool), }); + LevelOneRepository = repositoriesByModelName.LevelOne as Repository; + LevelTwoRepository = repositoriesByModelName.LevelTwo as Repository; + LevelThreeRepository = repositoriesByModelName.LevelThree as Repository; ProductRepository = repositoriesByModelName.Product as Repository; ReadonlyProductRepository = repositoriesByModelName.ReadonlyProduct as ReadonlyRepository; ReadonlyKitchenSinkRepository = repositoriesByModelName.KitchenSink as ReadonlyRepository; @@ -244,7 +256,7 @@ describe('ReadonlyRepository', () => { id: product.id, }), ]); - should.exist(result); + assert(result); result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); @@ -1337,7 +1349,8 @@ describe('ReadonlyRepository', () => { }); assert(result); result.should.deep.equal(simpleQueryResult); - result.message?.id.should.equal(simple.message.id); + assert(result.message); + result.message.id.should.equal(simple.message.id); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","store_id" AS "store","message" FROM "simple" WHERE ("name"=$1 AND "id"=$2) AND "id"=$3 LIMIT 1'); @@ -1808,6 +1821,77 @@ describe('ReadonlyRepository', () => { result1[0].instanceFunction().should.equal(`${result.name} bar!`); result2[0].instanceFunction().should.equal(`${result.name} bar!`); }); + it('should allow types when used in promise.all with other queries', async () => { + const three1: LevelThree = { + id: `three1: ${faker.datatype.uuid()}`, + three: `three1: ${faker.datatype.uuid()}`, + foo: `three1: ${faker.datatype.uuid()}`, + }; + const three2: LevelThree = { + id: `three2: ${faker.datatype.uuid()}`, + three: `three2: ${faker.datatype.uuid()}`, + foo: `three2: ${faker.datatype.uuid()}`, + }; + const two: LevelTwo = { + id: `two: ${faker.datatype.uuid()}`, + two: `two: ${faker.datatype.uuid()}`, + foo: `two: ${faker.datatype.uuid()}`, + levelThree: three1.id, + }; + const one: LevelOne = { + id: `one: ${faker.datatype.uuid()}`, + one: `one: ${faker.datatype.uuid()}`, + foo: `one: ${faker.datatype.uuid()}`, + levelTwo: two.id, + }; + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([one]), getQueryResult([two]), getQueryResult([three1, three2])); + + assert(three1.foo); + assert(three2.foo); + + const [ones, twoResult, threes] = await Promise.all([ + LevelOneRepository.find({ + select: ['one'], + }).where({ + foo: [one.foo, two.foo, three1.foo.toUpperCase(), three2.foo.toUpperCase()], + }), + LevelTwoRepository.findOne(), + LevelThreeRepository.find({ + select: ['three', 'foo'], + }).where({ + foo: [three1.foo, three2.foo], + }), + ]); + + verify(mockedPool.query(anyString(), anything())).thrice(); + ones.should.deep.equal([one]); + ones.length.should.equal(1); + ones[0].one.should.deep.equal(one.one); + + assert(twoResult); + twoResult.should.deep.equal(two); + twoResult.two.should.deep.equal(two.two); + + threes.should.deep.equal([three1, three2]); + threes.length.should.equal(2); + threes[0].three.should.equal(three1.three); + threes[1].three.should.equal(three2.three); + + const [levelOneQuery, levelOneQueryParams] = capture(mockedPool.query).first(); + levelOneQuery.should.equal('SELECT "one","id" FROM "level_one" WHERE "foo"=ANY($1::TEXT[])'); + assert(levelOneQueryParams); + levelOneQueryParams.should.deep.equal([[one.foo, two.foo, three1.foo.toUpperCase(), three2.foo.toUpperCase()]]); + + const [levelTwoQuery, levelTwoQueryParams] = capture(mockedPool.query).second(); + levelTwoQuery.should.equal('SELECT "id","two","foo","level_three_id" AS "levelThree" FROM "level_two" LIMIT 1'); + assert(levelTwoQueryParams); + levelTwoQueryParams.should.deep.equal([]); + + const [levelThreeQuery, levelThreeQueryParams] = capture(mockedPool.query).third(); + levelThreeQuery.should.equal('SELECT "three","foo","id" FROM "level_three" WHERE "foo"=ANY($1::TEXT[])'); + assert(levelThreeQueryParams); + levelThreeQueryParams.should.deep.equal([[three1.foo, three2.foo]]); + }); it('should support retaining original field - UNSAFE_withOriginalFieldType()', async () => { const store = new Store(); store.id = faker.datatype.number(); @@ -1861,6 +1945,10 @@ describe('ReadonlyRepository', () => { let translation1: SimpleWithSelfReference; let translation2: SimpleWithSelfReference; + let levelOneItem: LevelOne; + let levelTwoItem: LevelTwo; + let levelThreeItem: LevelThree; + before(() => { store1 = new Store(); store1.id = faker.datatype.number(); @@ -1957,6 +2045,20 @@ describe('ReadonlyRepository', () => { translation2.id = faker.datatype.uuid(); translation2.name = 'translation2'; translation2.source = source1.id; + + levelThreeItem = new LevelThree(); + levelThreeItem.id = faker.datatype.uuid(); + levelThreeItem.three = `Three - ${faker.datatype.uuid()}`; + + levelTwoItem = new LevelTwo(); + levelTwoItem.id = faker.datatype.uuid(); + levelTwoItem.two = `Two - ${faker.datatype.uuid()}`; + levelTwoItem.levelThree = levelThreeItem.id; + + levelOneItem = new LevelOne(); + levelOneItem.id = faker.datatype.uuid(); + levelOneItem.one = `One - ${faker.datatype.uuid()}`; + levelOneItem.levelTwo = levelTwoItem.id; }); it('should support populating a single relation - same/shared', async () => { @@ -2558,7 +2660,7 @@ describe('ReadonlyRepository', () => { translationsQuery.should.equal('SELECT "id","name","source_id" AS "source" FROM "simple" WHERE "source_id"=ANY($1::TEXT[])'); translationsQueryParams!.should.deep.equal([[source1.id, source2.id]]); }); - it('should support populating self reference and not explicitly selecting relation column', async () => { + it('should throw when attempting to populate collection and not not explicitly specifying relation column', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([_.pick(source1, 'id', 'name'), _.pick(source2, 'id', 'name')]), getQueryResult([_.pick(translation1, 'id', 'name', 'source'), _.pick(translation2, 'id', 'name', 'source')]),