From 9c4f3bcf099d03bb653d26855e57d235f8f65617 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 13:39:09 -0600 Subject: [PATCH 01/20] Release 10.0.0-beta1 - Fix populated entities to be QueryResult - Improve return types when doing partial selects - Be less restrictive with dependency versions - Drop node 12 support --- CHANGELOG.md | 7 +++++++ src/types/ExcludeEntityCollections.ts | 6 +++++- src/types/ExcludeFunctions.ts | 6 ++++++ src/types/ExcludeFunctionsAndEntityCollections.ts | 8 -------- ...tFunctionsAndEntityCollections.ts => OmitFunctions.ts} | 6 ++++++ src/types/PickAsPopulated.ts | 3 --- 6 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 src/types/ExcludeFunctions.ts delete mode 100644 src/types/ExcludeFunctionsAndEntityCollections.ts rename src/types/{OmitFunctionsAndEntityCollections.ts => OmitFunctions.ts} (66%) delete mode 100644 src/types/PickAsPopulated.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8a353..8848faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 10.0.0-beta1 - 2022-01-03 + +- Fix populated entities to be QueryResult +- Improve return types when doing partial selects +- Be less restrictive with dependency versions +- Drop node 12 support + ## 9.2.4 - 2021-12-28 - Fix PromiseLike signatures. Remove ChainablePromiseLike diff --git a/src/types/ExcludeEntityCollections.ts b/src/types/ExcludeEntityCollections.ts index f378100..8ee5508 100644 --- a/src/types/ExcludeEntityCollections.ts +++ b/src/types/ExcludeEntityCollections.ts @@ -3,4 +3,8 @@ import type { Entity, NotEntityBrand } from '../Entity'; /** * Removes all entity collection properties. To be used as a re-map key function */ -export type ExcludeEntityCollections = T extends NotEntityBrand[] | undefined ? K : T extends Entity[] | undefined ? never : K; +export type ExcludeEntityCollections = T extends NotEntityBrand[] | undefined + ? K // Return the key if collection is NotEntityBrand + : T extends Entity[] | undefined + ? never // If T is an entity array, remove + : K; // Otherwise, return the key diff --git a/src/types/ExcludeFunctions.ts b/src/types/ExcludeFunctions.ts new file mode 100644 index 0000000..b350cbb --- /dev/null +++ b/src/types/ExcludeFunctions.ts @@ -0,0 +1,6 @@ +/** + * Removes all functions and entity collection properties. To be used as a re-map key function + */ +// NOTE: eslint does not like using `Function`. +// eslint-disable-next-line @typescript-eslint/ban-types +export type ExcludeFunctions = T extends Function ? never : K; diff --git a/src/types/ExcludeFunctionsAndEntityCollections.ts b/src/types/ExcludeFunctionsAndEntityCollections.ts deleted file mode 100644 index 4f2f1fd..0000000 --- a/src/types/ExcludeFunctionsAndEntityCollections.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Entity, NotEntityBrand } from '../Entity'; - -/** - * Removes all functions and entity collection properties. To be used as a re-map key function - */ -// NOTE: eslint does not like using `Function`. -// eslint-disable-next-line @typescript-eslint/ban-types -export type ExcludeFunctionsAndEntityCollections = T extends NotEntityBrand[] | undefined ? K : T extends Entity[] | undefined ? never : T extends Function ? never : K; diff --git a/src/types/OmitFunctionsAndEntityCollections.ts b/src/types/OmitFunctions.ts similarity index 66% rename from src/types/OmitFunctionsAndEntityCollections.ts rename to src/types/OmitFunctions.ts index fafe71a..d73ba77 100644 --- a/src/types/OmitFunctionsAndEntityCollections.ts +++ b/src/types/OmitFunctions.ts @@ -6,3 +6,9 @@ import type { ExcludeFunctionsAndEntityCollections } from './ExcludeFunctionsAnd export type OmitFunctionsAndEntityCollections = { [K in keyof T as ExcludeFunctionsAndEntityCollections]: T[K]; }; + +/* +export type OmitFunctionsAndEntityCollections = { + [P in K as ExcludeFunctionsAndEntityCollections]: T[P]; +}; + */ diff --git a/src/types/PickAsPopulated.ts b/src/types/PickAsPopulated.ts deleted file mode 100644 index 637baa5..0000000 --- a/src/types/PickAsPopulated.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Populated } from './Populated'; - -export type PickAsPopulated = Populated, TProperty>; From 79b5183394eb410de6401c683030549d7560b286 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 14:36:39 -0600 Subject: [PATCH 02/20] Fix: Do not include optional entity collections --- src/types/QueryResult.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/QueryResult.ts b/src/types/QueryResult.ts index 65d6434..5a6835c 100644 --- a/src/types/QueryResult.ts +++ b/src/types/QueryResult.ts @@ -8,7 +8,7 @@ import type { ExcludeEntityCollections } from './ExcludeEntityCollections'; */ export type QueryResult = Extract< { - [K in keyof T as ExcludeEntityCollections]: T[K] extends NotEntityBrand | undefined ? T[K] : Exclude; + [K in keyof T as ExcludeEntityCollections, K>]: T[K] extends NotEntityBrand | undefined ? T[K] : Exclude; }, T >; From 01ff64f46a0de99044f924693c97d44d714faaa0 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 14:48:12 -0600 Subject: [PATCH 03/20] Add some documentation --- src/types/GetValueType.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/types/GetValueType.ts b/src/types/GetValueType.ts index ef7af34..9f7b6e3 100644 --- a/src/types/GetValueType.ts +++ b/src/types/GetValueType.ts @@ -1 +1,7 @@ -export type GetValueType = T extends TValueType[] ? (T extends (infer U)[] ? Extract : never) : T extends TValueType ? T : never; +export type GetValueType = T extends TValueType[] // If type is an array + ? T extends (infer U)[] // Infer the array item type + ? Extract // Return the array item type as a TValueType if array type can be inferred + : never // Unable to infer the array type + : T extends TValueType + ? T + : never; From 7fdd06d382d16750388957dc37555f5c9812aae8 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 15:15:21 -0600 Subject: [PATCH 04/20] Constrain types based on selected columns * Use a TReturn generic to track the return type from findOne() and find() * Split OmitFunctionsAndEntityCollections to separate types * Always include "id" with populated field types --- src/IReadonlyRepository.ts | 20 ++++++- src/ReadonlyRepository.ts | 94 +++++++++++++++---------------- src/Repository.ts | 6 +- src/SqlHelper.ts | 12 ++-- src/query/DeleteOptions.ts | 12 ++-- src/query/FindArgs.ts | 2 +- src/query/FindOneArgs.ts | 6 +- src/query/FindOneResult.ts | 10 ++-- src/query/FindResult.ts | 8 +-- src/query/PopulateArgs.ts | 6 +- src/query/ReturnSelect.ts | 6 +- src/query/Sort.ts | 16 +++--- src/query/WhereQuery.ts | 22 ++++---- src/types/CreateUpdateParams.ts | 5 +- src/types/IncludeFunctions.ts | 6 ++ src/types/OmitFunctions.ts | 14 ++--- src/types/PickFunctions.ts | 5 ++ src/types/Populated.ts | 19 +++---- src/types/QueryResultPopulated.ts | 11 +++- src/types/index.ts | 7 ++- 20 files changed, 154 insertions(+), 133 deletions(-) create mode 100644 src/types/IncludeFunctions.ts create mode 100644 src/types/PickFunctions.ts diff --git a/src/IReadonlyRepository.ts b/src/IReadonlyRepository.ts index 8cfe91c..dce4e4e 100644 --- a/src/IReadonlyRepository.ts +++ b/src/IReadonlyRepository.ts @@ -1,7 +1,7 @@ import type { Entity } from './Entity'; import type { ModelMetadata } from './metadata'; import type { CountResult, FindArgs, FindOneArgs, FindOneResult, FindResult, WhereQuery } from './query'; -import type { QueryResult } from './types/QueryResult'; +import type { QueryResult } from './types'; export interface IReadonlyRepository { readonly model: ModelMetadata; @@ -13,7 +13,14 @@ export interface IReadonlyRepository { * @param {object} [args.where] - Object representing the where query * @param {string|object|string[]|object[]} [args.sort] - Property name(s) to sort by */ - findOne(args: FindOneArgs | WhereQuery): FindOneResult>; + findOne< + // Optional keys specified as args.select + K extends string & keyof T, + // Return type used to pass through to all chained methods + TReturn = QueryResult> | null, + >( + args: FindOneArgs | WhereQuery, + ): FindOneResult; /** * Gets a collection of objects @@ -24,7 +31,14 @@ export interface IReadonlyRepository { * @param {string|number} [args.skip] - Number of records to skip * @param {string|number} [args.limit] - Number of results to return */ - find(args: FindArgs | WhereQuery): FindResult>; + find< + // Optional keys specified as args.select + K extends string & keyof T, + // Return type used to pass through to all chained methods + TReturn = QueryResult>, + >( + args: FindArgs | WhereQuery, + ): FindResult; /** * Gets a count of rows matching the where query diff --git a/src/ReadonlyRepository.ts b/src/ReadonlyRepository.ts index d73afb5..f9c44ae 100644 --- a/src/ReadonlyRepository.ts +++ b/src/ReadonlyRepository.ts @@ -7,7 +7,7 @@ import type { IRepository } from './IRepository'; import type { ColumnCollectionMetadata, ColumnModelMetadata, ColumnTypeMetadata, ModelMetadata } from './metadata'; import type { CountResult, FindArgs, FindOneArgs, FindOneResult, FindResult, OrderBy, PaginateOptions, PopulateArgs, Sort, WhereQuery, SortObject, SortObjectValue } from './query'; import { getCountQueryAndParams, getSelectQueryAndParams } from './SqlHelper'; -import type { GetValueType, PickByValueType, OmitFunctionsAndEntityCollections, QueryResult, PickAsPopulated, PickAsType } from './types'; +import type { GetValueType, PickByValueType, QueryResult, PickAsType, OmitEntityCollections, OmitFunctions, PickFunctions, Populated } from './types'; export interface IRepositoryOptions { modelMetadata: ModelMetadata; @@ -71,10 +71,10 @@ export class ReadonlyRepository implements IReadonlyRepository * @param {object} [args.where] - Object representing the where query * @param {string|object} [args.sort] - Property name(s) to sort by */ - public findOne(args: FindOneArgs | WhereQuery = {}): FindOneResult> { + public findOne | 'id'>> | null>(args: FindOneArgs | WhereQuery = {}): FindOneResult { const { stack } = new Error(`${this.model.name}.findOne()`); - let select: Set> | undefined; + let select: Set | undefined; let where: WhereQuery = {}; let sort: SortObject | string | null = null; let poolOverride: Pool | undefined; @@ -85,7 +85,7 @@ export class ReadonlyRepository implements IReadonlyRepository switch (name) { case 'select': if (value) { - select = new Set(value as (string & keyof OmitFunctionsAndEntityCollections)[]); + select = new Set(value as string[]); } break; @@ -129,7 +129,7 @@ export class ReadonlyRepository implements IReadonlyRepository * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery): FindOneResult> { + where(value: WhereQuery): FindOneResult { where = value; return this; @@ -144,15 +144,15 @@ export class ReadonlyRepository implements IReadonlyRepository * @param {string|number} [options.skip] - Number of records to skip * @param {string|number} [options.limit] - Number of results to return */ - populate & keyof T>( + populate & keyof T, TPopulateType extends GetValueType, TPopulateSelectKeys extends keyof TPopulateType>( propertyName: TProperty, - options?: PopulateArgs[TProperty], Entity>>, - ): FindOneResult, TProperty> & PickAsPopulated> { + options?: PopulateArgs, + ): FindOneResult & Populated> { // Add the column if the property is a single relation and not included in the list of select columns - if (select && !select.has(propertyName as unknown as string & keyof OmitFunctionsAndEntityCollections)) { + if (select && !select.has(propertyName)) { for (const column of modelInstance.model.columns) { if ((column as ColumnModelMetadata).model && column.propertyName === propertyName) { - select.add(column.propertyName as string & keyof OmitFunctionsAndEntityCollections); + select.add(column.propertyName); } } } @@ -167,37 +167,35 @@ export class ReadonlyRepository implements IReadonlyRepository pool: options?.pool || poolOverride, }); - return this as FindOneResult, TProperty> & PickAsPopulated>; + return this as FindOneResult & Populated>; }, /** * Sorts the query * @param {string|object} [value] */ - sort(value?: Sort): FindOneResult> { + sort(value?: Sort): FindOneResult { if (value) { sorts.push(...modelInstance._convertSortsToOrderBy(value)); } return this; }, - UNSAFE_withOriginalFieldType & keyof T>( - _propertyName: TProperty, - ): FindOneResult, TProperty> & Pick> { - return this; + UNSAFE_withOriginalFieldType & keyof T>(_propertyName: TProperty): FindOneResult & Pick> { + return this as FindOneResult & Pick>; }, UNSAFE_withFieldValue( propertyName: TProperty, value: TValue, - ): FindOneResult, TProperty> & PickAsType> { + ): FindOneResult & PickAsType> { manuallySetFields.push({ propertyName, value, }); - return this as FindOneResult, TProperty> & PickAsType>; + return this as FindOneResult & PickAsType>; }, - async then | null, TErrorResult = void>( - resolve: (result: QueryResult | null) => PromiseLike | TResult, + async then( + resolve: (result: TReturn | null) => PromiseLike | TResult, reject: (error: Error) => PromiseLike | TErrorResult, ): Promise { try { @@ -208,7 +206,7 @@ export class ReadonlyRepository implements IReadonlyRepository const { query, params } = getSelectQueryAndParams({ repositoriesByModelNameLowered: modelInstance._repositoriesByModelNameLowered, model: modelInstance.model, - select: select ? Array.from(select) : undefined, + select: select ? (Array.from(select) as (string & keyof OmitFunctions>)[]) : undefined, where, sorts, limit: 1, @@ -230,7 +228,7 @@ export class ReadonlyRepository implements IReadonlyRepository result[manuallySetField.propertyName as string & keyof T] = manuallySetField.value; } - return await resolve(result); + return await resolve(result as unknown as TReturn); } return await resolve(null); @@ -257,10 +255,10 @@ export class ReadonlyRepository implements IReadonlyRepository * @param {string|number} [args.skip] - Number of records to skip * @param {string|number} [args.limit] - Number of results to return */ - public find(args: FindArgs | WhereQuery = {}): FindResult> { + public find | 'id'>>>(args: FindArgs | WhereQuery = {}): FindResult { const { stack } = new Error(`${this.model.name}.find()`); - let select: Set> | undefined; + let select: Set | undefined; let where: WhereQuery = {}; let sort: SortObject | string | null = null; let skip: number | null = null; @@ -273,7 +271,7 @@ export class ReadonlyRepository implements IReadonlyRepository switch (name) { case 'select': if (value) { - select = new Set(value as (string & keyof OmitFunctionsAndEntityCollections)[]); + select = new Set(value as string[]); } break; @@ -318,7 +316,7 @@ export class ReadonlyRepository implements IReadonlyRepository * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery): FindResult> { + where(value: WhereQuery): FindResult { where = value; return this; @@ -333,15 +331,16 @@ export class ReadonlyRepository implements IReadonlyRepository * @param {string|number} [options.skip] - Number of records to skip * @param {string|number} [options.limit] - Number of results to return */ - populate & keyof T>( - propertyName: TProperty, - options?: PopulateArgs[TProperty], Entity>>, - ): FindResult, TProperty> & PickAsPopulated> { + populate< + TProperty extends string & keyof PickByValueType & keyof T, + TPopulateType extends GetValueType, + TPopulateSelectKeys extends string & keyof TPopulateType, + >(propertyName: TProperty, options?: PopulateArgs): FindResult & Populated> { // Add the column if the property is a single relation and not included in the list of select columns - if (select && !select.has(propertyName as unknown as string & keyof OmitFunctionsAndEntityCollections)) { + if (select && !select.has(propertyName)) { for (const column of modelInstance.model.columns) { if ((column as ColumnModelMetadata).model && column.propertyName === propertyName) { - select.add(column.propertyName as string & keyof OmitFunctionsAndEntityCollections); + select.add(column.propertyName); } } } @@ -356,14 +355,13 @@ export class ReadonlyRepository implements IReadonlyRepository pool: options?.pool || poolOverride, }); - // TODO: Figure out the type to make this happy without having to cast to unknown - return this as unknown as FindResult, TProperty> & PickAsPopulated>; + return this as unknown as FindResult & Populated>; }, /** * Sorts the query * @param {string|string[]|object} [value] */ - sort(value?: Sort): FindResult> { + sort(value?: Sort): FindResult { if (value) { sorts.push(...modelInstance._convertSortsToOrderBy(value)); } @@ -374,7 +372,7 @@ export class ReadonlyRepository implements IReadonlyRepository * Limits results returned by the query * @param {number} value */ - limit(value: number): FindResult> { + limit(value: number): FindResult { limit = value; return this; @@ -383,27 +381,25 @@ export class ReadonlyRepository implements IReadonlyRepository * Skips records returned by the query * @param {number} value */ - skip(value: number): FindResult> { + skip(value: number): FindResult { skip = value; return this; }, - UNSAFE_withOriginalFieldType & keyof T>( - _propertyName: TProperty, - ): FindResult, TProperty> & Pick> { - return this; + UNSAFE_withOriginalFieldType & keyof T>(_propertyName: TProperty): FindResult & Pick> { + return this as unknown as FindResult & Pick>; }, /** * Pages records returned by the query * @param {number} [page=1] - Page to return - Starts at 1 * @param {number} [limit=10] - Number of records to return */ - paginate({ page = 1, limit: paginateLimit = 10 }: PaginateOptions): FindResult> { + paginate({ page = 1, limit: paginateLimit = 10 }: PaginateOptions): FindResult { const safePage = Math.max(page, 1); return this.skip(safePage * paginateLimit - paginateLimit).limit(paginateLimit); }, - async then[], TErrorResult = void>( - resolve: (result: QueryResult[]) => PromiseLike | TResult, + async then( + resolve: (result: TReturn[]) => PromiseLike | TResult, reject: (error: Error) => PromiseLike | TErrorResult, ): Promise { try { @@ -414,7 +410,7 @@ export class ReadonlyRepository implements IReadonlyRepository const { query, params } = getSelectQueryAndParams({ repositoriesByModelNameLowered: modelInstance._repositoriesByModelNameLowered, model: modelInstance.model, - select: select ? Array.from(select) : undefined, + select: select ? (Array.from(select) as (string & keyof OmitFunctions>)[]) : undefined, where, sorts, skip: skip || 0, @@ -429,7 +425,7 @@ export class ReadonlyRepository implements IReadonlyRepository await modelInstance.populateFields(entities, populates); } - return await resolve(entities); + return await resolve(entities as unknown as TReturn[]); } catch (ex) { const typedException = ex as Error; if (typedException.stack) { @@ -556,7 +552,7 @@ export class ReadonlyRepository implements IReadonlyRepository if (Array.isArray(sorts)) { for (const sort of sorts as string[]) { const parts = sort.trim().split(' '); - const propertyName = parts.shift() as string & keyof OmitFunctionsAndEntityCollections; + const propertyName = parts.shift() as string & keyof OmitFunctions>; result.push({ propertyName, descending: /desc/i.test(parts.join('')), @@ -565,7 +561,7 @@ export class ReadonlyRepository implements IReadonlyRepository } else if (_.isString(sorts)) { for (const sort of sorts.split(',')) { const parts = sort.trim().split(' '); - const propertyName = parts.shift() as string & keyof OmitFunctionsAndEntityCollections; + const propertyName = parts.shift() as string & keyof OmitFunctions>; result.push({ propertyName, descending: /desc/i.test(parts.join('')), @@ -580,7 +576,7 @@ export class ReadonlyRepository implements IReadonlyRepository } result.push({ - propertyName: propertyName as string & keyof OmitFunctionsAndEntityCollections, + propertyName: propertyName as string & keyof OmitFunctions>, descending, }); } diff --git a/src/Repository.ts b/src/Repository.ts index c039fe2..15dde04 100644 --- a/src/Repository.ts +++ b/src/Repository.ts @@ -12,7 +12,7 @@ import type { } from './query'; import { ReadonlyRepository } from './ReadonlyRepository'; import { getDeleteQueryAndParams, getInsertQueryAndParams, getUpdateQueryAndParams } from './SqlHelper'; -import type { CreateUpdateParams, OmitFunctionsAndEntityCollections, QueryResult } from './types'; +import type { CreateUpdateParams, QueryResult, OmitEntityCollections, OmitFunctions } from './types'; export class Repository extends ReadonlyRepository implements IRepository { /** @@ -70,7 +70,7 @@ export class Repository extends ReadonlyRepository implemen } let returnRecords = true; - let returnSelect: (string & keyof OmitFunctionsAndEntityCollections)[] | undefined; + let returnSelect: (string & keyof OmitFunctions>)[] | undefined; if (options) { if ((options as DoNotReturnRecords).returnRecords === false) { returnRecords = false; @@ -147,7 +147,7 @@ export class Repository extends ReadonlyRepository implemen } let returnRecords = true; - let returnSelect: (string & keyof OmitFunctionsAndEntityCollections)[] | undefined; + let returnSelect: (string & keyof OmitFunctions>)[] | undefined; if (options) { if ((options as DoNotReturnRecords).returnRecords === false) { returnRecords = false; diff --git a/src/SqlHelper.ts b/src/SqlHelper.ts index 00e1f01..3dad3f8 100644 --- a/src/SqlHelper.ts +++ b/src/SqlHelper.ts @@ -10,7 +10,7 @@ import type { ModelMetadata, } from './metadata'; import type { Comparer, OrderBy, WhereClauseValue, WhereQuery } from './query'; -import type { CreateUpdateParams, OmitFunctionsAndEntityCollections } from './types'; +import type { CreateUpdateParams, OmitEntityCollections, OmitFunctions } from './types'; interface QueryAndParams { query: string; @@ -42,7 +42,7 @@ export function getSelectQueryAndParams({ }: { repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; - select?: readonly (string & keyof OmitFunctionsAndEntityCollections)[]; + select?: readonly (string & keyof OmitFunctions>)[]; where?: WhereQuery; sorts: readonly OrderBy[]; skip: number; @@ -169,7 +169,7 @@ export function getInsertQueryAndParams({ model: ModelMetadata; values: CreateUpdateParams | CreateUpdateParams[]; returnRecords?: boolean; - returnSelect?: readonly (string & keyof OmitFunctionsAndEntityCollections)[]; + returnSelect?: readonly (string & keyof OmitFunctions>)[]; }): QueryAndParams { const entitiesToInsert = _.isArray(values) ? values : [values]; const columnsToInsert = []; @@ -317,7 +317,7 @@ export function getUpdateQueryAndParams({ where: WhereQuery; values: CreateUpdateParams; returnRecords?: boolean; - returnSelect?: (string & keyof OmitFunctionsAndEntityCollections)[]; + returnSelect?: (string & keyof OmitFunctions>)[]; }): QueryAndParams { for (const column of model.updateDateColumns) { if (_.isUndefined(values[column.propertyName as string & keyof CreateUpdateParams])) { @@ -437,7 +437,7 @@ export function getDeleteQueryAndParams({ model: ModelMetadata; where?: WhereQuery; returnRecords?: boolean; - returnSelect?: readonly (string & keyof OmitFunctionsAndEntityCollections)[]; + returnSelect?: readonly (string & keyof OmitFunctions>)[]; }): QueryAndParams { let query = `DELETE FROM "${model.tableName}"`; @@ -473,7 +473,7 @@ export function getDeleteQueryAndParams({ * @returns {string} SQL columns * @private */ -export function getColumnsToSelect({ model, select }: { model: ModelMetadata; select?: readonly (string & keyof OmitFunctionsAndEntityCollections)[] }): string { +export function getColumnsToSelect({ model, select }: { model: ModelMetadata; select?: readonly (string & keyof OmitFunctions>)[] }): string { let selectColumns: Set; if (select) { const { primaryKeyColumn } = model; diff --git a/src/query/DeleteOptions.ts b/src/query/DeleteOptions.ts index d4a453b..6b46843 100644 --- a/src/query/DeleteOptions.ts +++ b/src/query/DeleteOptions.ts @@ -1,14 +1,14 @@ import type { Entity } from '../Entity'; -import type { OmitFunctionsAndEntityCollections } from '../types'; +import type { OmitEntityCollections, OmitFunctions } from '../types'; -interface ReturnSelect { - returnSelect: (string & keyof OmitFunctionsAndEntityCollections)[]; +interface ReturnSelect { + returnSelect: (K & string & keyof OmitFunctions>)[]; returnRecords?: true; } -interface ReturnRecords { +interface ReturnRecords { returnRecords: true; - returnSelect?: (string & keyof OmitFunctionsAndEntityCollections)[]; + returnSelect?: (K & string & keyof OmitFunctions>)[]; } -export type DeleteOptions = ReturnRecords | ReturnSelect; +export type DeleteOptions = ReturnRecords | ReturnSelect; diff --git a/src/query/FindArgs.ts b/src/query/FindArgs.ts index 23f18a7..68ed791 100644 --- a/src/query/FindArgs.ts +++ b/src/query/FindArgs.ts @@ -2,7 +2,7 @@ import type { Entity } from '../Entity'; import type { FindOneArgs } from './FindOneArgs'; -export interface FindArgs extends FindOneArgs { +export interface FindArgs extends FindOneArgs { skip?: number; limit?: number; } diff --git a/src/query/FindOneArgs.ts b/src/query/FindOneArgs.ts index 7f750a1..1489e3d 100644 --- a/src/query/FindOneArgs.ts +++ b/src/query/FindOneArgs.ts @@ -1,13 +1,13 @@ import type { Pool } from 'postgres-pool'; import type { Entity } from '../Entity'; -import type { OmitFunctionsAndEntityCollections } from '../types'; +import type { OmitEntityCollections, OmitFunctions } from '../types'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface FindOneArgs { - select?: (string & keyof OmitFunctionsAndEntityCollections)[]; +export interface FindOneArgs> & keyof T> { + select?: (K & string & keyof OmitFunctions>)[]; where?: WhereQuery; sort?: Sort; pool?: Pool; diff --git a/src/query/FindOneResult.ts b/src/query/FindOneResult.ts index 3bed372..826c69c 100644 --- a/src/query/FindOneResult.ts +++ b/src/query/FindOneResult.ts @@ -1,5 +1,5 @@ import type { Entity } from '../Entity'; -import type { GetValueType, PickAsPopulated, PickByValueType, PickAsType, QueryResult } from '../types'; +import type { GetValueType, PickByValueType, PickAsType, Populated } from '../types'; import type { PopulateArgs } from './PopulateArgs'; import type { Sort } from './Sort'; @@ -7,14 +7,14 @@ import type { WhereQuery } from './WhereQuery'; export interface FindOneResult extends PromiseLike { where(args: WhereQuery): FindOneResult; - populate & keyof T>( + populate & keyof T, TPopulateType extends GetValueType, TPopulateSelectKeys extends string & keyof TPopulateType>( propertyName: TProperty, - options?: PopulateArgs[TProperty], Entity>>, - ): FindOneResult & PickAsPopulated>; + options?: PopulateArgs, + ): FindOneResult & Populated>; sort(value?: Sort): FindOneResult; UNSAFE_withOriginalFieldType & keyof T>(propertyName: TProperty): FindOneResult & Pick>; UNSAFE_withFieldValue( propertyName: TProperty, value: TValue, - ): FindOneResult, TProperty> & PickAsType>; + ): FindOneResult & PickAsType>; } diff --git a/src/query/FindResult.ts b/src/query/FindResult.ts index b6153e0..641f043 100644 --- a/src/query/FindResult.ts +++ b/src/query/FindResult.ts @@ -1,5 +1,5 @@ import type { Entity } from '../Entity'; -import type { PickByValueType, GetValueType, PickAsPopulated } from '../types'; +import type { PickByValueType, GetValueType, Populated } from '../types'; import type { PaginateOptions } from './PaginateOptions'; import type { PopulateArgs } from './PopulateArgs'; @@ -8,10 +8,10 @@ import type { WhereQuery } from './WhereQuery'; export interface FindResult extends PromiseLike { where(args: WhereQuery): FindResult; - populate & keyof T>( + populate & keyof T, TPopulateType extends GetValueType, TPopulateSelectKeys extends string & keyof TPopulateType>( propertyName: TProperty, - options?: PopulateArgs[TProperty], Entity>>, - ): FindResult & PickAsPopulated>; + options?: PopulateArgs, + ): FindResult & Populated>; sort(value?: Sort): FindResult; limit(value: number): FindResult; skip(value: number): FindResult; diff --git a/src/query/PopulateArgs.ts b/src/query/PopulateArgs.ts index 0062374..014f086 100644 --- a/src/query/PopulateArgs.ts +++ b/src/query/PopulateArgs.ts @@ -1,14 +1,14 @@ import type { Pool } from 'postgres-pool'; import type { Entity } from '../Entity'; -import type { OmitFunctionsAndEntityCollections } from '../types'; +import type { OmitEntityCollections, OmitFunctions } from '../types'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface PopulateArgs { +export interface PopulateArgs { where?: WhereQuery; - select?: (string & keyof OmitFunctionsAndEntityCollections)[]; + select?: (K & string & keyof OmitFunctions>)[]; sort?: Sort; skip?: number; limit?: number; diff --git a/src/query/ReturnSelect.ts b/src/query/ReturnSelect.ts index 2fe3394..031a7a5 100644 --- a/src/query/ReturnSelect.ts +++ b/src/query/ReturnSelect.ts @@ -1,6 +1,6 @@ import type { Entity } from '../Entity'; -import type { OmitFunctionsAndEntityCollections } from '../types'; +import type { OmitEntityCollections, OmitFunctions } from '../types'; -export interface ReturnSelect { - returnSelect: (string & keyof OmitFunctionsAndEntityCollections)[]; +export interface ReturnSelect> = keyof OmitFunctions>> { + returnSelect: (K & string)[]; } diff --git a/src/query/Sort.ts b/src/query/Sort.ts index 1f3d330..2c4f8eb 100644 --- a/src/query/Sort.ts +++ b/src/query/Sort.ts @@ -1,12 +1,12 @@ import type { Entity } from '../Entity'; -import type { ExcludeFunctionsAndEntityCollections, OmitFunctionsAndEntityCollections } from '../types'; +import type { ExcludeFunctions, OmitEntityCollections, OmitFunctions } from '../types'; export type SortString = - | `${string & keyof OmitFunctionsAndEntityCollections} ASC` - | `${string & keyof OmitFunctionsAndEntityCollections} asc` - | `${string & keyof OmitFunctionsAndEntityCollections} DESC` - | `${string & keyof OmitFunctionsAndEntityCollections} desc` - | `${string & keyof OmitFunctionsAndEntityCollections}`; + | `${string & keyof OmitFunctions>} ASC` + | `${string & keyof OmitFunctions>} asc` + | `${string & keyof OmitFunctions>} DESC` + | `${string & keyof OmitFunctions>} desc` + | `${string & keyof OmitFunctions>}`; type ValidateMultipleSorts< T extends Entity, @@ -28,12 +28,12 @@ export type MultipleSortString = { - [K in keyof T as ExcludeFunctionsAndEntityCollections]?: SortObjectValue; + [K in keyof T as ExcludeFunctions, K>]?: SortObjectValue; }; export type Sort = MultipleSortString | SortObject; export interface OrderBy { - propertyName: string & keyof OmitFunctionsAndEntityCollections; + propertyName: string & keyof OmitFunctions>; descending?: boolean; } diff --git a/src/query/WhereQuery.ts b/src/query/WhereQuery.ts index 5a8048a..211404f 100644 --- a/src/query/WhereQuery.ts +++ b/src/query/WhereQuery.ts @@ -1,15 +1,10 @@ import type { Entity, NotEntityBrand } from '../Entity'; -import type { EntityPrimitiveOrId, ExcludeFunctionsAndEntityCollections, QueryResult } from '../types'; +import type { EntityPrimitiveOrId, ExcludeEntityCollections, ExcludeFunctions } from '../types'; export type WhereClauseValue = TValue extends NotEntityBrand | undefined ? Exclude : TValue extends Entity - ? - | Exclude, undefined> - | Exclude, undefined>[] - | Exclude, 'id'>, undefined> - | Exclude, 'id'>, undefined>[] - | null + ? { id: unknown }[] | Exclude, undefined> | Exclude, undefined>[] | { id: unknown } | null : Exclude<(TValue | null)[] | TValue, undefined> | null; export type StringConstraint = { @@ -33,12 +28,15 @@ export type WhereQueryStatement = TValue extends string : NegatableConstraint>; export type WhereQuery = { - [K in keyof T as ExcludeFunctionsAndEntityCollections]?: K extends 'id' - ? WhereQueryStatement - : T[K] extends (infer U)[] | undefined + // Exclude entity collections and functions. Make the rest of the properties optional + [K in keyof T as ExcludeEntityCollections>]?: K extends 'id' + ? WhereQueryStatement // Allow nested where query statements + : T[K] extends (infer U)[] | undefined // If property type is an array, allow where query statements for the array type ? WhereQueryStatement - : // NOTE: The extra parts (| Exclude<...>) at the end of the next line are needed for arrays of union types - Exclude<(T[K] | null)[] | T[K], undefined> | WhereQueryStatement | { '!': Exclude<(T[K] | null)[] | T[K], undefined> }; + : + | Exclude<(T[K] | null)[] | T[K], undefined> // Allow arrays or single object of type + | WhereQueryStatement // Allow nested where query statements + | { '!': Exclude<(T[K] | null)[] | T[K], undefined> }; // Allow arrays of union types } & { or?: WhereQuery[]; }; diff --git a/src/types/CreateUpdateParams.ts b/src/types/CreateUpdateParams.ts index ed1af02..88161a8 100644 --- a/src/types/CreateUpdateParams.ts +++ b/src/types/CreateUpdateParams.ts @@ -1,14 +1,15 @@ import type { Entity, NotEntityBrand } from '../Entity'; import type { EntityPrimitiveOrId } from './EntityPrimitiveOrId'; -import type { ExcludeFunctionsAndEntityCollections } from './ExcludeFunctionsAndEntityCollections'; +import type { ExcludeEntityCollections } from './ExcludeEntityCollections'; +import type { ExcludeFunctions } from './ExcludeFunctions'; import type { QueryResult } from './QueryResult'; /** * Changes all Entity value properties to Primitive (string|number) | Pick */ export type CreateUpdateParams = { - [K in keyof T as ExcludeFunctionsAndEntityCollections]?: T[K] extends NotEntityBrand | undefined + [K in keyof T as ExcludeEntityCollections>]?: T[K] extends NotEntityBrand | undefined ? T[K] : T[K] extends Entity ? EntityPrimitiveOrId | Pick, 'id'> diff --git a/src/types/IncludeFunctions.ts b/src/types/IncludeFunctions.ts new file mode 100644 index 0000000..af42891 --- /dev/null +++ b/src/types/IncludeFunctions.ts @@ -0,0 +1,6 @@ +/** + * Returns the key name if the property type is a function + */ +// NOTE: eslint does not like using `Function`. +// eslint-disable-next-line @typescript-eslint/ban-types +export type IncludeFunctions = T extends Function ? K : never; diff --git a/src/types/OmitFunctions.ts b/src/types/OmitFunctions.ts index d73ba77..f6fcbb0 100644 --- a/src/types/OmitFunctions.ts +++ b/src/types/OmitFunctions.ts @@ -1,14 +1,8 @@ -import type { ExcludeFunctionsAndEntityCollections } from './ExcludeFunctionsAndEntityCollections'; +import type { ExcludeFunctions } from './ExcludeFunctions'; /** - * Removes all functions and entity collection properties + * Removes all functions */ -export type OmitFunctionsAndEntityCollections = { - [K in keyof T as ExcludeFunctionsAndEntityCollections]: T[K]; +export type OmitFunctions = { + [K in keyof T as ExcludeFunctions]: T[K]; }; - -/* -export type OmitFunctionsAndEntityCollections = { - [P in K as ExcludeFunctionsAndEntityCollections]: T[P]; -}; - */ diff --git a/src/types/PickFunctions.ts b/src/types/PickFunctions.ts new file mode 100644 index 0000000..a1260dd --- /dev/null +++ b/src/types/PickFunctions.ts @@ -0,0 +1,5 @@ +import type { IncludeFunctions } from './IncludeFunctions'; + +export type PickFunctions = { + [K in keyof T as IncludeFunctions]: T[K]; +}; diff --git a/src/types/Populated.ts b/src/types/Populated.ts index 3452bc3..3d48773 100644 --- a/src/types/Populated.ts +++ b/src/types/Populated.ts @@ -1,16 +1,15 @@ import type { Entity } from '../Entity'; +import type { PickFunctions } from './PickFunctions'; +import type { QueryResult } from './QueryResult'; + /** * Removes primitives from specified properties and make non-optional. Allow singular Entity properties to be null. */ -export type Populated = Omit & { - // Removes optional from property - // If T[P] is not an array: - // If the property is originally optional, include null as a possible value type - // Otherwise, do not include null as a possible value type - [P in K]-?: Extract extends Entity - ? undefined extends T[P] - ? Exclude, undefined> | null - : Extract - : Exclude, undefined>; +export type Populated = { + [P in K]-?: Extract extends Entity // Remove optional from property + ? undefined extends T[P] // If property is not an array + ? QueryResult | 'id'>> | null // If the property is originally optional, include null as a possible value type + : QueryResult | 'id'>> // Otherwise, use the TPropertyType + : QueryResult | 'id'>>[]; // Otherwise return an array of items }; diff --git a/src/types/QueryResultPopulated.ts b/src/types/QueryResultPopulated.ts index e5f23a8..a5777ff 100644 --- a/src/types/QueryResultPopulated.ts +++ b/src/types/QueryResultPopulated.ts @@ -1,9 +1,16 @@ import type { Entity } from '../Entity'; -import type { PickAsPopulated } from './PickAsPopulated'; +import type { GetValueType } from './GetValueType'; import type { QueryResult } from './QueryResult'; /** * Allows a QueryResult type with specific populated properties */ -export type QueryResultPopulated = Omit, K> & PickAsPopulated; +export type QueryResultPopulated = Omit, K> & { + // NOTE: This is very similar to Populated. Main difference is that it calls GetValueType<> for each key specified and does not support sub selects + [P in K]-?: Extract extends Entity // Remove optional from property + ? undefined extends T[P] // If property is not an array + ? QueryResult> | null // If the property is originally optional, include null as a possible value type + : QueryResult> // Otherwise, use the TPropertyType + : QueryResult>[]; // Otherwise return an array of items +}; diff --git a/src/types/index.ts b/src/types/index.ts index 301f53c..574c80c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,14 +1,15 @@ export * from './CreateUpdateParams'; export * from './EntityPrimitiveOrId'; export * from './ExcludeEntityCollections'; -export * from './ExcludeFunctionsAndEntityCollections'; +export * from './ExcludeFunctions'; export * from './GetValueType'; +export * from './IncludeFunctions'; export * from './IsValueOfType'; export * from './OmitEntityCollections'; -export * from './OmitFunctionsAndEntityCollections'; +export * from './OmitFunctions'; export * from './PickAsType'; -export * from './PickAsPopulated'; export * from './PickByValueType'; +export * from './PickFunctions'; export * from './Populated'; export * from './QueryResult'; export * from './QueryResultPopulated'; From 37765001bda8b89d8d6f292442be3b4406cd8582 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 15:22:16 -0600 Subject: [PATCH 05/20] Replace should.exist() with assert() --- tests/readonlyRepository.tests.ts | 193 ++++++++++++++++-------------- 1 file changed, 100 insertions(+), 93 deletions(-) diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index 5b011b3..2d3c6b6 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -3,11 +3,11 @@ import assert from 'assert'; import chai from 'chai'; import * as faker from 'faker'; import _ from 'lodash'; -import type { QueryResult } from 'pg'; +import type { QueryResult as PgQueryResult } from 'pg'; import { Pool } from 'postgres-pool'; import { anyString, anything, capture, instance, mock, reset, verify, when } from 'ts-mockito'; -import type { Repository, ReadonlyRepository, QueryResultPopulated, NotEntity } from '../src'; +import type { Repository, ReadonlyRepository, QueryResult, QueryResultPopulated, NotEntity } from '../src'; import { initialize } from '../src'; import type { IJsonLikeEntity } from './models'; @@ -33,7 +33,7 @@ import { TeacherClassroom, } from './models'; -function getQueryResult(rows: T[] = []): QueryResult { +function getQueryResult(rows: T[] = []): PgQueryResult { return { command: 'select', rowCount: 1, @@ -117,8 +117,8 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ReadonlyProductRepository.findOne(); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" LIMIT 1'); @@ -139,8 +139,8 @@ describe('ReadonlyRepository', () => { }, sort: 'name asc', }); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "name","id" FROM "products" WHERE "id"=$1 ORDER BY "name" LIMIT 1'); @@ -157,8 +157,8 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne({ id: product.id, }); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); @@ -174,8 +174,8 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne({ id: product, }); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); @@ -196,8 +196,8 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne({ store: productStore, }); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 LIMIT 1'); @@ -217,8 +217,8 @@ describe('ReadonlyRepository', () => { }).where({ id: product.id, }); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); verify(mockedPool.query(anyString(), anything())).never(); const [query, params] = capture(poolOverride.query).first(); @@ -236,8 +236,8 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne().where({ id: product.id, }); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); @@ -272,8 +272,8 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ProductRepository.findOne().sort('name asc'); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" ORDER BY "name" LIMIT 1'); @@ -295,9 +295,9 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); + assert(result); - result!.should.deep.equal({ + result.should.deep.equal({ id, name, intColumn: numberValue, @@ -324,8 +324,8 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); - result!.should.deep.equal({ + assert(result); + result.should.deep.equal({ id, name, intColumn: 42, @@ -352,8 +352,8 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); - result!.should.deep.equal({ + assert(result); + result.should.deep.equal({ id, name, intColumn: numberValue, @@ -380,8 +380,8 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); - result!.should.deep.equal({ + assert(result); + result.should.deep.equal({ id, name, intColumn: largeNumberValue, @@ -408,8 +408,8 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); - result!.should.deep.equal({ + assert(result); + result.should.deep.equal({ id, name, floatColumn: numberValue, @@ -436,8 +436,8 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); - result!.should.deep.equal({ + assert(result); + result.should.deep.equal({ id, name, floatColumn: numberValue, @@ -464,8 +464,8 @@ describe('ReadonlyRepository', () => { ); const result = await ReadonlyKitchenSinkRepository.findOne(); - should.exist(result); - result!.should.deep.equal({ + assert(result); + result.should.deep.equal({ id, name, floatColumn: largeNumberValue, @@ -502,8 +502,8 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne().populate('store'); verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -541,8 +541,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); verify(poolOverride.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -580,8 +580,8 @@ describe('ReadonlyRepository', () => { }); verify(mockedPool.query(anyString(), anything())).once(); verify(storePool.query(anyString(), anything())).once(); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -618,8 +618,8 @@ describe('ReadonlyRepository', () => { select: ['name'], }); verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "name","store_id" AS "store","id" FROM "products" LIMIT 1'); @@ -655,8 +655,8 @@ describe('ReadonlyRepository', () => { sort: 'name', }); verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(product); + assert(result); + result.should.deep.equal(product); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -682,15 +682,18 @@ describe('ReadonlyRepository', () => { const storeWithProducts: QueryResultPopulated = { ...store, - products: [product1, product2], + products: [product1 as QueryResult, product2 as QueryResult], }; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1, product2])); const result = await StoreRepository.findOne().populate('products'); verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(storeWithProducts); + assert(result); + result.should.deep.equal(storeWithProducts); + result.products.length.should.equal(2); + result.products[0].id.should.equal(product1.id); + result.products[1].id.should.equal(product2.id); storeWithProducts.products.length.should.equal(2); const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); @@ -719,7 +722,7 @@ describe('ReadonlyRepository', () => { const storeWithProducts: QueryResultPopulated = { ...store, - products: [product1, product2], + products: [product1 as QueryResult, product2 as QueryResult], }; when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1, product2])); @@ -730,8 +733,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); verify(poolOverride.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(storeWithProducts); + assert(result); + result.should.deep.equal(storeWithProducts); storeWithProducts.products.length.should.equal(2); const [storeQuery, storeQueryParams] = capture(poolOverride.query).first(); @@ -760,7 +763,7 @@ describe('ReadonlyRepository', () => { const storeWithProducts: QueryResultPopulated = { ...store, - products: [product1, product2], + products: [product1 as QueryResult, product2 as QueryResult], }; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store])); @@ -770,8 +773,8 @@ describe('ReadonlyRepository', () => { pool: instance(productPool), }); verify(mockedPool.query(anyString(), anything())).once(); - should.exist(result); - result!.should.deep.equal(storeWithProducts); + assert(result); + result.should.deep.equal(storeWithProducts); storeWithProducts.products.length.should.equal(2); const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); @@ -811,8 +814,8 @@ describe('ReadonlyRepository', () => { sort: 'aliases', }); verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(storeWithProducts); + assert(result); + result.should.deep.equal(storeWithProducts); const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); @@ -856,8 +859,8 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne().populate('categories'); verify(mockedPool.query(anyString(), anything())).thrice(); - should.exist(result); - result!.should.deep.equal(productWithCategories); + assert(result); + result.should.deep.equal(productWithCategories); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -909,8 +912,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); verify(poolOverride.query(anyString(), anything())).thrice(); - should.exist(result); - result!.should.deep.equal(productWithCategories); + assert(result); + result.should.deep.equal(productWithCategories); const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -964,8 +967,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).once(); verify(categoryPool.query(anyString(), anything())).twice(); - should.exist(result); - result!.should.deep.equal(productWithCategories); + assert(result); + result.should.deep.equal(productWithCategories); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -1015,8 +1018,8 @@ describe('ReadonlyRepository', () => { sort: 'name desc', }); verify(mockedPool.query(anyString(), anything())).thrice(); - should.exist(result); - result!.should.deep.equal(productWithCategories); + assert(result); + result.should.deep.equal(productWithCategories); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -1182,8 +1185,8 @@ describe('ReadonlyRepository', () => { }) .sort('store desc'); verify(mockedPool.query(anyString(), anything())).times(4); - should.exist(result); - result!.should.deep.equal(fullProduct); + assert(result); + result.should.deep.equal(fullProduct); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 ORDER BY "store_id" DESC LIMIT 1'); @@ -1210,11 +1213,11 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result1); - result1!.should.deep.equal(result2); - result1!.instanceFunction().should.equal(`${result.name} bar!`); - should.exist(result2); - result2!.instanceFunction().should.equal(`${result.name} bar!`); + assert(result1); + result1.should.deep.equal(result2); + result1.instanceFunction().should.equal(`${result.name} bar!`); + assert(result2); + result2.instanceFunction().should.equal(`${result.name} bar!`); }); it('should not create an object/assign instance functions to null results', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([null])); @@ -1252,8 +1255,8 @@ describe('ReadonlyRepository', () => { }, ], }); - should.exist(result); - result!.should.deep.equal(simple); + assert(result); + result.should.deep.equal(simple); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","other_ids" AS "otherIds" FROM "simple" WHERE (("id"=$1) OR (($2=ANY("other_ids") OR $3=ANY("other_ids")))) LIMIT 1'); @@ -1464,7 +1467,7 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.find(); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1498,7 +1501,7 @@ describe('ReadonlyRepository', () => { skip: 5, limit: 24, }); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1526,7 +1529,7 @@ describe('ReadonlyRepository', () => { id: _.map(products, 'id'), store, }); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1550,7 +1553,7 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.find({ pool: instance(poolOverride), }); - should.exist(result); + assert(result); result.should.deep.equal(products); verify(mockedPool.query(anyString(), anything())).never(); @@ -1578,7 +1581,7 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.find().where({ store: store.id, }); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1617,7 +1620,7 @@ describe('ReadonlyRepository', () => { like: ['Foo', 'BAR'], }, }); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1648,7 +1651,7 @@ describe('ReadonlyRepository', () => { }, }, }); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1677,7 +1680,7 @@ describe('ReadonlyRepository', () => { store: store.id, }), ]); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1698,7 +1701,7 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.find().sort('name asc'); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1719,7 +1722,7 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.find().limit(42); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1740,7 +1743,7 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.find().skip(24); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1764,7 +1767,7 @@ describe('ReadonlyRepository', () => { page: 3, limit: 100, }); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1798,7 +1801,7 @@ describe('ReadonlyRepository', () => { .sort('store desc'); verify(mockedPool.query(anyString(), anything())).once(); - should.exist(result); + assert(result); result.should.deep.equal(products); const [query, params] = capture(mockedPool.query).first(); @@ -1815,8 +1818,8 @@ describe('ReadonlyRepository', () => { const result1 = await ReadonlyKitchenSinkRepository.find(); const result2 = await ReadonlyKitchenSinkRepository.find(); verify(mockedPool.query(anyString(), anything())).twice(); - should.exist(result1); - should.exist(result2); + assert(result1); + assert(result2); result1.should.deep.equal(result2); result1[0].instanceFunction().should.equal(`${result.name} bar!`); result2[0].instanceFunction().should.equal(`${result.name} bar!`); @@ -2564,8 +2567,7 @@ describe('ReadonlyRepository', () => { categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); - const boxedResults = results as QueryResultPopulated[]; - boxedResults[0].store.id.should.equal(store1.id); + results[0].store.id.should.equal(store1.id); }); it('should support populating multiple properties with partial select and sort', async () => { when(mockedPool.query(anyString(), anything())) @@ -2574,7 +2576,12 @@ describe('ReadonlyRepository', () => { .thenResolve(getQueryResult([teacher1Classroom])) .thenResolve(getQueryResult([_.pick(classroom, 'id', 'name')])); - async function getTeachers(): Promise[]> { + async function getTeachers(): Promise< + (Omit, 'parkingSpace'> & { + parkingSpace: QueryResult> | null; + classrooms: QueryResult>[]; + })[] + > { return TeacherRepository.find() .where({ isActive: true, @@ -2709,7 +2716,7 @@ describe('ReadonlyRepository', () => { ); const result = await ProductRepository.count(); - should.exist(result); + assert(result); result.should.equal(products.length); const [query, params] = capture(mockedPool.query).first(); @@ -2744,7 +2751,7 @@ describe('ReadonlyRepository', () => { id: _.map(products, 'id'), store, }); - should.exist(result); + assert(result); result.should.equal(products.length); const [query, params] = capture(mockedPool.query).first(); @@ -2778,7 +2785,7 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.count().where({ store: store.id, }); - should.exist(result); + assert(result); result.should.equal(products.length); const [query, params] = capture(mockedPool.query).first(); @@ -2814,7 +2821,7 @@ describe('ReadonlyRepository', () => { store: store.id, }), ]); - should.exist(result); + assert(result); result.should.equal(products.length); const [query, params] = capture(mockedPool.query).first(); From 715915c12dc75819ec0cd223a7701508dfd906b9 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 18:17:38 -0600 Subject: [PATCH 06/20] Make sure entity collections are optional --- README.md | 31 ++++++++++++++++++++++++++++++- tests/models/Category.ts | 4 ++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cbc3be1..44a6821 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ export class Product extends Entity { defaultsTo: [], name: 'alias_names', }) - public aliases!: string[]; + public aliases?: string[]; @column({ model: () => Store.name, @@ -555,6 +555,35 @@ const items = await PersonRepository.destroy( ### Known issues +#### Entity collections must be optional + +BigAl expects that all entity collection properties must be optional. There will be some type issues with QueryResult +if you make a collection non-optional. + +For example: + +```ts +export class Store extends Entity { + @primaryColumn({ type: 'integer' }) + public id!: number; + + @column({ + type: 'string', + required: true, + }) + public name!: string; + + // This property MUST be optional + @column({ + collection: () => Product.name, + via: 'store', + }) + public products?: Product[]; +} +``` + +#### Non-entity object arrays + If you have a json property, with an `id` field, on an entity model, TypeScript will probably think it is a BigAl entity due to how the type system works. In that case, you'll want to wrap the type with `NotEntity<>`. For example: diff --git a/tests/models/Category.ts b/tests/models/Category.ts index 074298d..ee25a58 100644 --- a/tests/models/Category.ts +++ b/tests/models/Category.ts @@ -1,4 +1,4 @@ -import { column, table } from '../../src/decorators'; +import { column, table } from '../../src'; import { ModelBase } from './ModelBase'; // eslint-disable-next-line import/no-cycle @@ -21,5 +21,5 @@ export class Category extends ModelBase { through: () => ProductCategory.name, via: 'category', }) - public products!: Product[]; + public products?: Product[]; } From c61b1da28989db72d9c75c36079dc5172a20da4a Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Mon, 3 Jan 2022 18:22:49 -0600 Subject: [PATCH 07/20] Formatting updates --- src/types/ExcludeEntityCollections.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/ExcludeEntityCollections.ts b/src/types/ExcludeEntityCollections.ts index 8ee5508..8eb84c6 100644 --- a/src/types/ExcludeEntityCollections.ts +++ b/src/types/ExcludeEntityCollections.ts @@ -4,7 +4,7 @@ import type { Entity, NotEntityBrand } from '../Entity'; * Removes all entity collection properties. To be used as a re-map key function */ export type ExcludeEntityCollections = T extends NotEntityBrand[] | undefined - ? K // Return the key if collection is NotEntityBrand + ? K // Return the key if collection is a NotEntityBrand array : T extends Entity[] | undefined ? never // If T is an entity array, remove : K; // Otherwise, return the key From 68d5b4fc0966debbbee83127ff17c9c123213706 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 13:48:07 -0600 Subject: [PATCH 08/20] Use generator utility to generate data for tests --- tests/models/ParkingLot.ts | 15 + tests/models/ParkingSpace.ts | 13 + tests/models/index.ts | 1 + tests/readonlyRepository.tests.ts | 1552 +++++++++++------------------ tests/utils/generator.ts | 178 ++++ 5 files changed, 784 insertions(+), 975 deletions(-) create mode 100644 tests/models/ParkingLot.ts create mode 100644 tests/utils/generator.ts diff --git a/tests/models/ParkingLot.ts b/tests/models/ParkingLot.ts new file mode 100644 index 0000000..5a0b2bc --- /dev/null +++ b/tests/models/ParkingLot.ts @@ -0,0 +1,15 @@ +import { column, primaryColumn, table, Entity } from '../../src'; + +@table({ + name: 'parking_lot', +}) +export class ParkingLot extends Entity { + @primaryColumn({ type: 'string' }) + public id!: string; + + @column({ + type: 'string', + required: true, + }) + public name!: string; +} diff --git a/tests/models/ParkingSpace.ts b/tests/models/ParkingSpace.ts index 87568f5..79a3ad5 100644 --- a/tests/models/ParkingSpace.ts +++ b/tests/models/ParkingSpace.ts @@ -1,5 +1,7 @@ import { column, primaryColumn, table, Entity } from '../../src'; +import { ParkingLot } from './ParkingLot'; + @table({ name: 'parking_space', }) @@ -7,9 +9,20 @@ export class ParkingSpace extends Entity { @primaryColumn({ type: 'string' }) public id!: string; + @column({ + model: () => ParkingLot.name, + name: 'parking_lot_id', + required: true, + }) + public parkingLot!: ParkingLot | string; + @column({ type: 'string', required: true, }) public name!: string; + + public getLotAndName(): string { + return `${typeof this.parkingLot === 'string' ? this.parkingLot : this.parkingLot.id} - ${this.name}`; + } } diff --git a/tests/models/index.ts b/tests/models/index.ts index 3b31f41..9b5aa9d 100644 --- a/tests/models/index.ts +++ b/tests/models/index.ts @@ -4,6 +4,7 @@ export * from './KitchenSink'; export * from './LevelOne'; export * from './LevelTwo'; export * from './LevelThree'; +export * from './ParkingLot'; export * from './ParkingSpace'; export * from './Product'; export * from './ProductCategory'; diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index 2d3c6b6..2ec54f0 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -7,10 +7,10 @@ import type { QueryResult as PgQueryResult } from 'pg'; import { Pool } from 'postgres-pool'; import { anyString, anything, capture, instance, mock, reset, verify, when } from 'ts-mockito'; -import type { Repository, ReadonlyRepository, QueryResult, QueryResultPopulated, NotEntity } from '../src'; +import type { Repository, ReadonlyRepository, QueryResult, QueryResultPopulated } from '../src'; import { initialize } from '../src'; -import type { IJsonLikeEntity } from './models'; +import type { ParkingLot } from './models'; import { Category, Classroom, @@ -26,12 +26,12 @@ import { SimpleWithRelationAndJson, SimpleWithSelfReference, SimpleWithStringCollection, - SimpleWithStringId, SimpleWithUnion, Store, Teacher, TeacherClassroom, } from './models'; +import * as generator from './utils/generator'; function getQueryResult(rows: T[] = []): PgQueryResult { return { @@ -109,12 +109,16 @@ describe('ReadonlyRepository', () => { }); describe('#findOne()', () => { - it('should support call without constraints', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; + let store: QueryResult; + let product: QueryResult; + beforeEach(() => { + store = generator.store(); + product = generator.product({ + store: store.id, + }); + }); + it('should support call without constraints', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ReadonlyProductRepository.findOne(); assert(result); @@ -125,12 +129,8 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([]); }); it('should support call with constraints as a parameter', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); + const productResult = _.pick(product, 'id', 'name'); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([productResult])); const result = await ProductRepository.findOne({ select: ['name'], @@ -140,18 +140,27 @@ describe('ReadonlyRepository', () => { sort: 'name asc', }); assert(result); - result.should.deep.equal(product); + result.should.deep.equal(productResult); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "name","id" FROM "products" WHERE "id"=$1 ORDER BY "name" LIMIT 1'); params!.should.deep.equal([product.id]); }); - it('should support call with where constraint as a parameter', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; + it('should support call with sort as a parameter', async () => { + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); + const result = await ProductRepository.findOne({ + sort: 'name', + }); + assert(result); + result.should.deep.equal(product); + result.name.should.equal(product.name); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" LIMIT 1 ORDER BY "name"'); + params!.should.deep.equal([product.id]); + }); + it('should support call with where constraint as a parameter', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ProductRepository.findOne({ @@ -165,10 +174,6 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([product.id]); }); it('should support call with where constraint as a parameter and querying id by entity value', async () => { - const product = new Product(); - product.id = faker.datatype.number(); - product.name = `product - ${faker.datatype.uuid()}`; - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ProductRepository.findOne({ @@ -182,33 +187,20 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([product.id]); }); it('should support call with where constraint as a parameter and querying property by entity value', async () => { - const productStore = new Store(); - productStore.id = faker.datatype.number(); - productStore.name = `store - ${faker.datatype.uuid()}`; - - const product = new Product(); - product.id = faker.datatype.number(); - product.name = `product - ${faker.datatype.uuid()}`; - product.store = productStore.id; - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ProductRepository.findOne({ - store: productStore, + store, }); assert(result); result.should.deep.equal(product); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 LIMIT 1'); - params!.should.deep.equal([productStore.id]); + params!.should.deep.equal([store.id]); }); it('should support call with explicit pool override', async () => { const poolOverride = mock(Pool); - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -226,11 +218,6 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([product.id]); }); it('should support call with chained where constraints', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ProductRepository.findOne().where({ @@ -244,11 +231,6 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([product.id]); }); it('should support call with chained where constraints - Promise.all', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const [result] = await Promise.all([ @@ -264,11 +246,6 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([product.id]); }); it('should support call with chained sort', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ProductRepository.findOne().sort('name asc'); @@ -479,31 +456,15 @@ describe('ReadonlyRepository', () => { }); }); it('should support populating a single relation', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store, - }; - - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id: product.id, - name: product.name, - store: store.id, - }, - ]), - getQueryResult([store]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([store])); const result = await ProductRepository.findOne().populate('store'); verify(mockedPool.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(product); + result.should.deep.equal({ + ...product, + store, + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -514,26 +475,8 @@ describe('ReadonlyRepository', () => { }); it('should support populating a single relation with implicit inherited pool override', async () => { const poolOverride = mock(Pool); - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store, - }; - when(poolOverride.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id: product.id, - name: product.name, - store: store.id, - }, - ]), - getQueryResult([store]), - ); + when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([store])); const result = await ProductRepository.findOne({ pool: instance(poolOverride), @@ -554,25 +497,7 @@ describe('ReadonlyRepository', () => { it('should support populating a single relation with explicit pool override', async () => { const storePool = mock(Pool); - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store, - }; - - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id: product.id, - name: product.name, - store: store.id, - }, - ]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); when(storePool.query(anyString(), anything())).thenResolve(getQueryResult([store])); const result = await ProductRepository.findOne().populate('store', { @@ -581,7 +506,10 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).once(); verify(storePool.query(anyString(), anything())).once(); assert(result); - result.should.deep.equal(product); + result.should.deep.equal({ + ...product, + store, + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -591,26 +519,9 @@ describe('ReadonlyRepository', () => { storeQueryParams!.should.deep.equal([store.id]); }); it('should support populating a single relation when column is missing from partial select', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store, - }; - - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id: product.id, - name: product.name, - store: store.id, - }, - ]), - getQueryResult([store]), - ); + const productResult = _.pick(product, 'id', 'name', 'store'); + const storeResult = _.pick(store, 'id', 'name'); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([productResult]), getQueryResult([storeResult])); const result = await ProductRepository.findOne({ select: ['name'], @@ -619,7 +530,10 @@ describe('ReadonlyRepository', () => { }); verify(mockedPool.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(product); + result.should.deep.equal({ + ...productResult, + store: storeResult, + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "name","store_id" AS "store","id" FROM "products" LIMIT 1'); @@ -628,27 +542,35 @@ describe('ReadonlyRepository', () => { storeQuery.should.equal('SELECT "name","id" FROM "stores" WHERE "id"=$1'); storeQueryParams!.should.deep.equal([store.id]); }); - it('should support populating a single relation with partial select and order', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store, - }; + it('should support populating a single relation as QueryResult with partial select', async () => { + const levelThreeItem = generator.levelThree(); + const levelTwoItem = generator.levelTwo({ levelThree: levelThreeItem.id }); + const levelOneItem = generator.levelOne({ levelTwo: levelTwoItem.id }); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id: product.id, - name: product.name, - store: store.id, - }, - ]), - getQueryResult([store]), - ); + const levelOneResult = _.pick(levelOneItem, 'id', 'one', 'levelTwo'); + const levelTwoResult = _.pick(levelTwoItem, 'id', 'two', 'levelThree'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([levelOneResult]), getQueryResult([levelTwoResult])); + + const result = await LevelOneRepository.findOne({ + select: ['one', 'levelTwo'], + }).populate('levelTwo', { + select: ['two', 'levelThree'], + }); + verify(mockedPool.query(anyString(), anything())).twice(); + assert(result); + result.should.deep.equal({ + ...levelOneResult, + levelTwo: levelTwoResult, + }); + + result.levelTwo.levelThree.should.equal(levelThreeItem.id); + // Verify string functions are available - aka, that the type is not LevelThree | string. + result.levelTwo.levelThree.toUpperCase().should.equal(levelThreeItem.id.toUpperCase()); + }); + it('should support populating a single relation with partial select and order', async () => { + const storeResult = _.pick(store, 'id', 'name'); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([store])); const result = await ProductRepository.findOne().populate('store', { select: ['name'], @@ -656,7 +578,10 @@ describe('ReadonlyRepository', () => { }); verify(mockedPool.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(product); + result.should.deep.equal({ + ...product, + store: storeResult, + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -666,23 +591,16 @@ describe('ReadonlyRepository', () => { storeQueryParams!.should.deep.equal([store.id]); }); it('should support populating collection', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = `store - ${faker.datatype.uuid()}`; - - const product1 = new Product(); - product1.id = faker.datatype.number(); - product1.name = `product - ${faker.datatype.uuid()}`; - product1.store = store.id; - - const product2 = new Product(); - product2.id = faker.datatype.number(); - product2.name = `product - ${faker.datatype.uuid()}`; - product2.store = store.id; + const product1 = generator.product({ + store: store.id, + }); + const product2 = generator.product({ + store: store.id, + }); const storeWithProducts: QueryResultPopulated = { ...store, - products: [product1 as QueryResult, product2 as QueryResult], + products: [product1, product2], }; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1, product2])); @@ -694,7 +612,10 @@ describe('ReadonlyRepository', () => { result.products.length.should.equal(2); result.products[0].id.should.equal(product1.id); result.products[1].id.should.equal(product2.id); + // Make sure QueryResultPopulated types look ok storeWithProducts.products.length.should.equal(2); + storeWithProducts.products[0].id.should.equal(product1.id); + storeWithProducts.products[1].id.should.equal(product2.id); const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); @@ -706,24 +627,12 @@ describe('ReadonlyRepository', () => { it('should support populating collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); - const store = new Store(); - store.id = faker.datatype.number(); - store.name = `store - ${faker.datatype.uuid()}`; - - const product1 = new Product(); - product1.id = faker.datatype.number(); - product1.name = `product - ${faker.datatype.uuid()}`; - product1.store = store.id; - - const product2 = new Product(); - product2.id = faker.datatype.number(); - product2.name = `product - ${faker.datatype.uuid()}`; - product2.store = store.id; - - const storeWithProducts: QueryResultPopulated = { - ...store, - products: [product1 as QueryResult, product2 as QueryResult], - }; + const product1 = generator.product({ + store: store.id, + }); + const product2 = generator.product({ + store: store.id, + }); when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1, product2])); @@ -734,8 +643,10 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); verify(poolOverride.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(storeWithProducts); - storeWithProducts.products.length.should.equal(2); + result.should.deep.equal({ + ...store, + products: [product1, product2], + }); const [storeQuery, storeQueryParams] = capture(poolOverride.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); @@ -747,24 +658,12 @@ describe('ReadonlyRepository', () => { it('should support populating collection with explicit pool override', async () => { const productPool = mock(Pool); - const store = new Store(); - store.id = faker.datatype.number(); - store.name = `store - ${faker.datatype.uuid()}`; - - const product1 = new Product(); - product1.id = faker.datatype.number(); - product1.name = `product - ${faker.datatype.uuid()}`; - product1.store = store.id; - - const product2 = new Product(); - product2.id = faker.datatype.number(); - product2.name = `product - ${faker.datatype.uuid()}`; - product2.store = store.id; - - const storeWithProducts: QueryResultPopulated = { - ...store, - products: [product1 as QueryResult, product2 as QueryResult], - }; + const product1 = generator.product({ + store: store.id, + }); + const product2 = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store])); when(productPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product2])); @@ -774,8 +673,10 @@ describe('ReadonlyRepository', () => { }); verify(mockedPool.query(anyString(), anything())).once(); assert(result); - result.should.deep.equal(storeWithProducts); - storeWithProducts.products.length.should.equal(2); + result.should.deep.equal({ + ...store, + products: [product1, product2], + }); const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); @@ -785,29 +686,17 @@ describe('ReadonlyRepository', () => { productQueryParams!.should.deep.equal([store.id]); }); it('should support populating collection with partial select and order', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product1 = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, + const product1 = generator.product({ store: store.id, - }; - const product2 = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, + }); + const product2 = generator.product({ store: store.id, - }; + }); - const storeWithProducts = _.extend( - { - products: [product1, product2], - }, - store, - ); + const product1Result = _.pick(product1, 'id', 'name'); + const product2Result = _.pick(product2, 'id', 'name'); - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1, product2])); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1Result, product2Result])); const result = await StoreRepository.findOne().populate('products', { select: ['name'], @@ -815,7 +704,10 @@ describe('ReadonlyRepository', () => { }); verify(mockedPool.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(storeWithProducts); + result.should.deep.equal({ + ...store, + products: [product1Result, product2Result], + }); const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); @@ -825,42 +717,20 @@ describe('ReadonlyRepository', () => { productQueryParams!.should.deep.equal([store.id]); }); it('should support populating multi-multi collection', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - const category1 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const category2 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const productCategory1Map = { - id: faker.datatype.number(), - product: product.id, - category: category1.id, - }; - const productCategory2Map = { - id: faker.datatype.number(), - product: product.id, - category: category2.id, - }; - - const productWithCategories = _.extend( - { - categories: [category1, category2], - }, - product, - ); + const category1 = generator.category(); + const category2 = generator.category(); + const productCategory1Map = generator.productCategory(product, category1); + const productCategory2Map = generator.productCategory(product, category2); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([productCategory1Map, productCategory2Map]), getQueryResult([category1, category2])); const result = await ProductRepository.findOne().populate('categories'); verify(mockedPool.query(anyString(), anything())).thrice(); assert(result); - result.should.deep.equal(productWithCategories); + result.should.deep.equal({ + ...product, + categories: [category1, category2], + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -874,35 +744,10 @@ describe('ReadonlyRepository', () => { }); it('should support populating multi-multi collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - const category1 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const category2 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const productCategory1Map = { - id: faker.datatype.number(), - product: product.id, - category: category1.id, - }; - const productCategory2Map = { - id: faker.datatype.number(), - product: product.id, - category: category2.id, - }; - - const productWithCategories = _.extend( - { - categories: [category1, category2], - }, - product, - ); + const category1 = generator.category(); + const category2 = generator.category(); + const productCategory1Map = generator.productCategory(product, category1); + const productCategory2Map = generator.productCategory(product, category2); when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([productCategory1Map, productCategory2Map]), getQueryResult([category1, category2])); @@ -913,7 +758,10 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); verify(poolOverride.query(anyString(), anything())).thrice(); assert(result); - result.should.deep.equal(productWithCategories); + result.should.deep.equal({ + ...product, + categories: [category1, category2], + }); const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -928,35 +776,10 @@ describe('ReadonlyRepository', () => { it('should support populating multi-multi collection with explicit pool override', async () => { const categoryPool = mock(Pool); - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - const category1 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const category2 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const productCategory1Map = { - id: faker.datatype.number(), - product: product.id, - category: category1.id, - }; - const productCategory2Map = { - id: faker.datatype.number(), - product: product.id, - category: category2.id, - }; - - const productWithCategories = _.extend( - { - categories: [category1, category2], - }, - product, - ); + const category1 = generator.category(); + const category2 = generator.category(); + const productCategory1Map = generator.productCategory(product, category1); + const productCategory2Map = generator.productCategory(product, category2); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); when(categoryPool.query(anyString(), anything())).thenResolve(getQueryResult([productCategory1Map, productCategory2Map]), getQueryResult([category1, category2])); @@ -968,7 +791,10 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).once(); verify(categoryPool.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(productWithCategories); + result.should.deep.equal({ + ...product, + categories: [category1, category2], + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -981,37 +807,19 @@ describe('ReadonlyRepository', () => { categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with partial select and order', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }; - const category1 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const category2 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const productCategory1Map = { - id: faker.datatype.number(), - product: product.id, - category: category1.id, - }; - const productCategory2Map = { - id: faker.datatype.number(), - product: product.id, - category: category2.id, - }; + const category1 = generator.category(); + const category2 = generator.category(); + const productCategory1Map = generator.productCategory(product, category1); + const productCategory2Map = generator.productCategory(product, category2); - const productWithCategories = _.extend( - { - categories: [category1, category2], - }, - product, - ); + const category1Result = _.pick(category1, 'id', 'name'); + const category2Result = _.pick(category2, 'id', 'name'); - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([productCategory1Map, productCategory2Map]), getQueryResult([category1, category2])); + when(mockedPool.query(anyString(), anything())).thenResolve( + getQueryResult([product]), + getQueryResult([productCategory1Map, productCategory2Map]), + getQueryResult([category1Result, category2Result]), + ); const result = await ProductRepository.findOne().populate('categories', { select: ['name'], @@ -1019,7 +827,10 @@ describe('ReadonlyRepository', () => { }); verify(mockedPool.query(anyString(), anything())).thrice(); assert(result); - result.should.deep.equal(productWithCategories); + result.should.deep.equal({ + ...product, + categories: [category1Result, category2Result], + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -1032,24 +843,19 @@ describe('ReadonlyRepository', () => { categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating self reference collection', async () => { - const source1 = new SimpleWithSelfReference(); - source1.id = faker.datatype.uuid(); - source1.name = 'Source'; - - const translation1 = new SimpleWithSelfReference(); - translation1.id = faker.datatype.uuid(); - translation1.name = 'translation1'; - translation1.source = source1.id; + const source1 = generator.simpleWithSelfReference(); + const translation1 = generator.simpleWithSelfReference({ + name: 'translation1', + source: source1.id, + }); + const translation2 = generator.simpleWithSelfReference({ + name: 'translation2', + source: source1.id, + }); - const translation2 = new SimpleWithSelfReference(); - translation2.id = faker.datatype.uuid(); - translation2.name = 'translation2'; - translation2.source = source1.id; + const source1Result = _.pick(source1, 'id', 'name'); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(source1, 'id', 'name')]), - getQueryResult([_.pick(translation1, 'id', 'name', 'source'), _.pick(translation2, 'id', 'name', 'source')]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([source1Result]), getQueryResult([translation1, translation2])); const result = await SimpleWithSelfReferenceRepository.findOne({ select: ['name'], @@ -1061,8 +867,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); assert(result); result.should.deep.equal({ - ..._.pick(source1, 'id', 'name'), - translations: [_.pick(translation1, 'id', 'name', 'source'), _.pick(translation2, 'id', 'name', 'source')], + ...source1Result, + translations: [translation1, translation2], }); result.translations.length.should.equal(2); result.translations[0].id.should.equal(translation1.id); @@ -1075,24 +881,21 @@ describe('ReadonlyRepository', () => { translationsQueryParams!.should.deep.equal([source1.id]); }); it('should support populating collection and not explicitly selecting relation column', async () => { - const source1 = new SimpleWithSelfReference(); - source1.id = faker.datatype.uuid(); - source1.name = 'Source'; - - const translation1 = new SimpleWithSelfReference(); - translation1.id = faker.datatype.uuid(); - translation1.name = 'translation1'; - translation1.source = source1.id; + const source1 = generator.simpleWithSelfReference(); + const translation1 = generator.simpleWithSelfReference({ + name: 'translation1', + source: source1.id, + }); + const translation2 = generator.simpleWithSelfReference({ + name: 'translation2', + source: source1.id, + }); - const translation2 = new SimpleWithSelfReference(); - translation2.id = faker.datatype.uuid(); - translation2.name = 'translation2'; - translation2.source = source1.id; + const source1Result = _.pick(source1, 'id', 'name'); + const translation1Result = _.pick(translation1, 'id', 'name'); + const translation2Result = _.pick(translation2, 'id', 'name'); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(source1, 'id', 'name')]), - getQueryResult([_.pick(translation1, 'id', 'name', 'source'), _.pick(translation2, 'id', 'name', 'source')]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([source1Result]), getQueryResult([translation1Result, translation2Result])); const result = await SimpleWithSelfReferenceRepository.findOne({ select: ['name'], @@ -1106,8 +909,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); assert(result); result.should.deep.equal({ - ..._.pick(source1, 'id', 'name'), - translations: [_.pick(translation1, 'id', 'name', 'source'), _.pick(translation2, 'id', 'name', 'source')], + ...source1Result, + translations: [translation1Result, translation2Result], }); result.translations.length.should.equal(2); result.translations[0].id.should.equal(translation1.id); @@ -1120,41 +923,10 @@ describe('ReadonlyRepository', () => { translationsQueryParams!.should.deep.equal([source1.id]); }); it('should support complex query with multiple chained modifiers', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store: store.id, - }; - const category1 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const category2 = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const productCategory1Map = { - id: faker.datatype.number(), - product: product.id, - category: category1.id, - }; - const productCategory2Map = { - id: faker.datatype.number(), - product: product.id, - category: category2.id, - }; - - const fullProduct = _.defaults( - { - store, - categories: [category1, category2], - }, - product, - ); + const category1 = generator.category(); + const category2 = generator.category(); + const productCategory1Map = generator.productCategory(product, category1); + const productCategory2Map = generator.productCategory(product, category2); when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([product]), @@ -1186,7 +958,11 @@ describe('ReadonlyRepository', () => { .sort('store desc'); verify(mockedPool.query(anyString(), anything())).times(4); assert(result); - result.should.deep.equal(fullProduct); + result.should.deep.equal({ + ...product, + store, + categories: [category1, category2], + }); const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 ORDER BY "store_id" DESC LIMIT 1'); @@ -1229,19 +1005,11 @@ describe('ReadonlyRepository', () => { should.not.exist(result); }); it('should allow querying required string array', async () => { - const anotherSimple = new SimpleWithStringId(); - anotherSimple.id = faker.datatype.uuid(); - anotherSimple.name = 'anotherSimple'; - - const otherSimple = new SimpleWithStringId(); - otherSimple.id = faker.datatype.uuid(); - otherSimple.name = 'otherSimple'; - otherSimple.otherId = anotherSimple; - - const simple = new SimpleWithStringCollection(); - simple.id = faker.datatype.number(); - simple.name = `product - ${faker.datatype.uuid()}`; - simple.otherIds = [faker.datatype.uuid(), faker.datatype.uuid()]; + const anotherSimple = generator.simpleWithStringId(); + const otherSimple = generator.simpleWithStringId({ + otherId: anotherSimple.id, + }); + const simple = generator.simpleWithStringCollection(); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([simple])); @@ -1251,7 +1019,7 @@ describe('ReadonlyRepository', () => { id: simple.id, }, { - otherIds: [otherSimple.id, otherSimple.otherId.id], + otherIds: [otherSimple.id, anotherSimple.id], }, ], }); @@ -1263,11 +1031,7 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([simple.id, otherSimple.id, anotherSimple.id]); }); it('should support an object with an enum/union field', async () => { - const simple = { - id: faker.datatype.number(), - name: `simple - ${faker.datatype.uuid()}`, - status: 'Foobar', - }; + const simple = generator.simpleWithUnion(); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([simple])); const result = await SimpleWithUnionRepository.findOne().where({ @@ -1281,11 +1045,7 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([['Bar', 'Foo']]); }); it('should support an object with negated enum/union field', async () => { - const simple = { - id: faker.datatype.number(), - name: `simple - ${faker.datatype.uuid()}`, - status: 'Foobar', - }; + const simple = generator.simpleWithUnion(); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([simple])); const result = await SimpleWithUnionRepository.findOne().where({ @@ -1301,13 +1061,7 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([['Bar', 'Foo']]); }); it('should support an object with a json field', async () => { - const simple = { - id: faker.datatype.number(), - name: `simple - ${faker.datatype.uuid()}`, - keyValue: { - foo: 42, - }, - }; + const simple = generator.simpleWithJson(); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([simple])); const result = await SimpleWithJsonRepository.findOne(); @@ -1320,27 +1074,11 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([]); }); it('should support an object with a json field (with id property)', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = `store - ${faker.datatype.uuid()}`; - - const simple = new SimpleWithRelationAndJson(); - simple.id = faker.datatype.number(); - simple.name = `simple - ${faker.datatype.uuid()}`; - simple.store = store; - simple.message = { - id: 'foo', - message: 'bar', - } as NotEntity; - - const simpleQueryResult = { - id: simple.id, - name: simple.name, + const simple = generator.simpleWithRelationAndJson({ store: store.id, - message: simple.message, - }; + }); - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([simpleQueryResult])); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([simple])); const result = await SimpleWithRelationAndJsonRepository.findOne().where({ or: [ { @@ -1351,7 +1089,7 @@ describe('ReadonlyRepository', () => { id: 42, }); assert(result); - result.should.deep.equal(simpleQueryResult); + result.should.deep.equal(simple); assert(result.message); result.message.id.should.equal(simple.message.id); @@ -1360,53 +1098,31 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([simple.name, simple.id, 42]); }); it('should support an object with a json field (with id property) and populate statement', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = `store - ${faker.datatype.uuid()}`; - - const simple = new SimpleWithRelationAndJson(); - simple.id = faker.datatype.number(); - simple.name = `simple - ${faker.datatype.uuid()}`; - simple.store = store; - simple.message = { - id: 'foo', - message: 'bar', - } as NotEntity; - - const simpleQueryResult = { - id: simple.id, - name: simple.name, + const simple = generator.simpleWithRelationAndJson({ store: store.id, - message: simple.message, - }; + }); - const storeQueryResult = { - id: store.id, - name: store.name, - }; + const storeResult = _.pick(store, 'id', 'name'); when(mockedPool.query(anyString(), anything())) - .thenResolve(getQueryResult([simpleQueryResult])) - .thenResolve(getQueryResult([storeQueryResult])); + .thenResolve(getQueryResult([simple])) + .thenResolve(getQueryResult([storeResult])); const result = await SimpleWithRelationAndJsonRepository.findOne().populate('store', { select: ['name'], }); assert(result); result.should.deep.equal({ - ...simpleQueryResult, - store: storeQueryResult, + ...simple, + store: storeResult, }); - 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" LIMIT 1'); params!.should.deep.equal([]); }); it('should support retaining original field - UNSAFE_withOriginalFieldType()', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = 'Store'; - when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { @@ -1430,10 +1146,6 @@ describe('ReadonlyRepository', () => { productResult.store.name?.should.equal(store.name); }); it('should support manually setting a field - UNSAFE_withFieldValue()', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = 'Store'; - when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { @@ -1453,16 +1165,19 @@ describe('ReadonlyRepository', () => { }); }); describe('#find()', () => { + let store: QueryResult; + beforeEach(() => { + store = generator.store(); + }); + it('should support call without constraints', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1475,19 +1190,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([]); }); it('should support call with constraints as a parameter', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1509,19 +1218,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([_.map(products, 'id'), store.id]); }); it('should support call with where constraint as a parameter', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1539,14 +1242,12 @@ describe('ReadonlyRepository', () => { it('should support call with explicit pool override', async () => { const poolOverride = mock(Pool); const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1562,19 +1263,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([]); }); it('should support call with chained where constraints', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1590,16 +1285,12 @@ describe('ReadonlyRepository', () => { }); it('should support call with chained where constraints - array ILIKE array of values', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - serialNumber: faker.datatype.uuid(), - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - serialNumber: faker.datatype.uuid(), - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1631,16 +1322,14 @@ describe('ReadonlyRepository', () => { }); it('should support call with chained where constraints - NOT ILIKE array of values', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, + generator.product({ + store: store.id, sku: faker.datatype.uuid(), - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, + }), + generator.product({ + store: store.id, sku: faker.datatype.uuid(), - }, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1659,19 +1348,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([['foo', 'bar']]); }); it('should support call with chained where constraints - Promise.all', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1689,14 +1372,12 @@ describe('ReadonlyRepository', () => { }); it('should support call with chained sort', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1710,14 +1391,12 @@ describe('ReadonlyRepository', () => { }); it('should support call with chained limit', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1731,14 +1410,12 @@ describe('ReadonlyRepository', () => { }); it('should support call with chained skip', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1752,14 +1429,12 @@ describe('ReadonlyRepository', () => { }); it('should support call with chained paginate', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1775,19 +1450,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([]); }); it('should support complex query with multiple chained modifiers', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -1825,28 +1494,20 @@ describe('ReadonlyRepository', () => { 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()}`, + const three1 = generator.levelThree({ foo: `three1: ${faker.datatype.uuid()}`, - }; - const three2: LevelThree = { - id: `three2: ${faker.datatype.uuid()}`, - three: `three2: ${faker.datatype.uuid()}`, + }); + const three2 = generator.levelThree({ foo: `three2: ${faker.datatype.uuid()}`, - }; - const two: LevelTwo = { - id: `two: ${faker.datatype.uuid()}`, - two: `two: ${faker.datatype.uuid()}`, + }); + const two = generator.levelTwo({ foo: `two: ${faker.datatype.uuid()}`, levelThree: three1.id, - }; - const one: LevelOne = { - id: `one: ${faker.datatype.uuid()}`, - one: `one: ${faker.datatype.uuid()}`, + }); + const one = generator.levelOne({ foo: `one: ${faker.datatype.uuid()}`, levelTwo: two.id, - }; + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([one]), getQueryResult([two]), getQueryResult([three1, three2])); assert(three1.foo); @@ -1896,20 +1557,11 @@ describe('ReadonlyRepository', () => { 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(); - store.name = 'Store'; + const product = generator.product({ + store: store.id, + }); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id: faker.datatype.number(), - name: 'Product', - store: store.id, - }, - ]), - getQueryResult([store]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([store])); const products = await ProductRepository.find().UNSAFE_withOriginalFieldType('store'); products.length.should.equal(1); @@ -1925,161 +1577,102 @@ describe('ReadonlyRepository', () => { productResult.store.name?.should.equal(store.name); }); describe('populate', () => { - let store1: Store; - let store2: Store; - let product1: Product; - let product2: Product; - let product3: Product; - let category1: Category; - let category2: Category; - let product1Category1: ProductCategory; - let product1Category2: ProductCategory; - let product2Category1: ProductCategory; - let product3Category1: ProductCategory; - - let teacher1: Teacher; - let teacher2: Teacher; - let parkingSpace: ParkingSpace; - let classroom: Classroom; - let teacher1Classroom: TeacherClassroom; - - let source1: SimpleWithSelfReference; - let source2: SimpleWithSelfReference; - let translation1: SimpleWithSelfReference; - let translation2: SimpleWithSelfReference; - - let levelOneItem: LevelOne; - let levelTwoItem: LevelTwo; - let levelThreeItem: LevelThree; + let store1: QueryResult; + let store2: QueryResult; + let product1: QueryResult; + let product2: QueryResult; + let product3: QueryResult; + let category1: QueryResult; + let category2: QueryResult; + let product1Category1: QueryResult; + let product1Category2: QueryResult; + let product2Category1: QueryResult; + let product3Category1: QueryResult; + + let teacher1: QueryResult; + let teacher2: QueryResult; + let parkingLot: QueryResult; + let parkingSpace: QueryResult; + let classroom: QueryResult; + let teacher1Classroom: QueryResult; + + let source1: QueryResult; + let source2: QueryResult; + let translation1: QueryResult; + let translation2: QueryResult; + + let levelOneItem: QueryResult; + let levelTwoItem: QueryResult; + let levelThreeItem: QueryResult; before(() => { - store1 = new Store(); - store1.id = faker.datatype.number(); - store1.name = `store1 - ${store1.id}`; - - store2 = new Store(); - store2.id = faker.datatype.number(); - store2.name = `store2 - ${store2.id}`; - - product1 = new Product(); - product1.id = faker.datatype.number(); - product1.name = `product1 - ${product1.id}`; - product1.store = store1.id; - - product2 = new Product(); - product2.id = faker.datatype.number(); - product2.name = `product2 - ${product2.id}`; - product2.store = store2.id; - - product3 = new Product(); - product3.id = faker.datatype.number(); - product3.name = `product3 - ${product2.id}`; - product3.store = store1.id; - - category1 = new Category(); - category1.id = faker.datatype.number(); - category1.name = `category1 - ${category1.id}`; - - category2 = new Category(); - category2.id = faker.datatype.number(); - category2.name = `category2 - ${category2.id}`; - - product1Category1 = new ProductCategory(); - product1Category1.id = faker.datatype.number(); - product1Category1.product = product1.id; - product1Category1.category = category1.id; - - product1Category2 = new ProductCategory(); - product1Category2.id = faker.datatype.number(); - product1Category2.product = product1.id; - product1Category2.category = category2.id; - - product2Category1 = new ProductCategory(); - product2Category1.id = faker.datatype.number(); - product2Category1.product = product2.id; - product2Category1.category = category1.id; - - product3Category1 = new ProductCategory(); - product3Category1.id = faker.datatype.number(); - product3Category1.product = product3.id; - product3Category1.category = category1.id; - - teacher1 = new Teacher(); - teacher1.id = faker.datatype.uuid(); - teacher1.firstName = faker.name.firstName(); - teacher1.lastName = faker.name.lastName(); - teacher1.isActive = true; - - teacher2 = new Teacher(); - teacher2.id = faker.datatype.uuid(); - teacher2.firstName = faker.name.firstName(); - teacher2.lastName = faker.name.lastName(); - teacher2.isActive = true; - - parkingSpace = new ParkingSpace(); - parkingSpace.id = faker.datatype.uuid(); - parkingSpace.name = faker.datatype.number().toString(); - - teacher1.parkingSpace = parkingSpace.id; - - classroom = new Classroom(); - classroom.id = faker.datatype.uuid(); - classroom.name = faker.datatype.number().toString(); - - teacher1Classroom = new TeacherClassroom(); - teacher1Classroom.id = faker.datatype.uuid(); - teacher1Classroom.teacher = teacher1.id; - teacher1Classroom.classroom = classroom.id; - - source1 = new SimpleWithSelfReference(); - source1.id = faker.datatype.uuid(); - source1.name = 'Source'; - - source2 = new SimpleWithSelfReference(); - source2.id = faker.datatype.uuid(); - source2.name = 'Source2'; - - translation1 = new SimpleWithSelfReference(); - translation1.id = faker.datatype.uuid(); - translation1.name = 'translation1'; - translation1.source = source1.id; - - translation2 = new SimpleWithSelfReference(); - 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; + store1 = generator.store(); + store2 = generator.store(); + + product1 = generator.product({ + store: store1.id, + }); + product2 = generator.product({ + store: store2.id, + }); + product3 = generator.product({ + store: store1.id, + }); + + category1 = generator.category(); + category2 = generator.category(); + + product1Category1 = generator.productCategory(product1.id, category1.id); + product1Category2 = generator.productCategory(product1.id, category2.id); + product2Category1 = generator.productCategory(product2, category1); + product3Category1 = generator.productCategory(product2, category1); + + parkingLot = generator.parkingLot(); + parkingSpace = generator.parkingSpace({ + parkingLot: parkingLot.id, + }); + + teacher1 = generator.teacher({ + parkingSpace: parkingSpace.id, + }); + teacher2 = generator.teacher(); + + classroom = generator.classroom(); + + teacher1Classroom = generator.teacherClassroom(teacher1, classroom); + + source1 = generator.simpleWithSelfReference(); + source2 = generator.simpleWithSelfReference(); + + translation1 = generator.simpleWithSelfReference({ + source: source1.id, + }); + translation2 = generator.simpleWithSelfReference({ + source: source1.id, + }); + + levelThreeItem = generator.levelThree(); + levelTwoItem = generator.levelTwo({ + levelThree: levelThreeItem.id, + }); + levelOneItem = generator.levelOne({ + levelTwo: levelTwoItem.id, + }); }); it('should support populating a single relation - same/shared', async () => { - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')]), - getQueryResult([_.pick(store1, 'id', 'name')]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3]), getQueryResult([store])); const results = await ProductRepository.find().populate('store'); verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name'), - store: store1, + ...product1, + store, }, { - ..._.pick(product3, 'id', 'name'), - store: store1, + ...product3, + store, }, ]); @@ -2092,11 +1685,11 @@ describe('ReadonlyRepository', () => { }); it('should support populating a single relation - different', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), + getQueryResult([product1, product2]), getQueryResult([ // NOTE: Swapping the order to make sure that order doesn't matter - _.pick(store2, 'id', 'name'), - _.pick(store1, 'id', 'name'), + store2, + store1, ]), ); @@ -2104,11 +1697,11 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name'), + ...product1, store: store1, }, { - ..._.pick(product2, 'id', 'name'), + ...product2, store: store2, }, ]); @@ -2122,10 +1715,8 @@ describe('ReadonlyRepository', () => { }); it('should support populating a single relation with implicit inherited pool override', async () => { const poolOverride = mock(Pool); - when(poolOverride.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')]), - getQueryResult([_.pick(store1, 'id', 'name')]), - ); + + when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3]), getQueryResult([store1])); const results = await ProductRepository.find({ pool: instance(poolOverride), @@ -2135,11 +1726,11 @@ describe('ReadonlyRepository', () => { verify(poolOverride.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name'), + ...product1, store: store1, }, { - ..._.pick(product3, 'id', 'name'), + ...product3, store: store1, }, ]); @@ -2153,8 +1744,9 @@ describe('ReadonlyRepository', () => { }); it('should support populating a single relation with explicit pool override', async () => { const storePool = mock(Pool); - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')])); - when(storePool.query(anyString(), anything())).thenResolve(getQueryResult([_.pick(store1, 'id', 'name')])); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3])); + when(storePool.query(anyString(), anything())).thenResolve(getQueryResult([store1])); const results = await ProductRepository.find().populate('store', { pool: instance(storePool), @@ -2164,11 +1756,11 @@ describe('ReadonlyRepository', () => { verify(storePool.query(anyString(), anything())).once(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name'), + ...product1, store: store1, }, { - ..._.pick(product3, 'id', 'name'), + ...product3, store: store1, }, ]); @@ -2180,11 +1772,33 @@ describe('ReadonlyRepository', () => { storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); storeQueryParams!.should.deep.equal([store1.id]); }); + it('should support populating a single relation as QueryResult with partial select', async () => { + const levelOneResult = _.pick(levelOneItem, 'id', 'one', 'levelTwo'); + const levelTwoResult = _.pick(levelTwoItem, 'id', 'two', 'levelThree'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([levelOneResult]), getQueryResult([levelTwoResult])); + + const results = await LevelOneRepository.find({ + select: ['one', 'levelTwo'], + }).populate('levelTwo', { + select: ['two', 'levelThree'], + }); + verify(mockedPool.query(anyString(), anything())).twice(); + results.should.deep.equal([ + { + ...levelOneResult, + levelTwo: levelTwoResult, + }, + ]); + + results[0].levelTwo.levelThree.should.equal(levelThreeItem.id); + results[0].levelTwo.levelThree.toUpperCase().should.equal(levelThreeItem.id.toUpperCase()); + }); it('should support populating a single relation with partial select and sort', async () => { - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - getQueryResult([_.pick(store1, 'id'), _.pick(store2, 'id')]), - ); + const store1Result = _.pick(store1, 'id'); + const store2Result = _.pick(store2, 'id'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product2]), getQueryResult([store1Result, store2Result])); const results = await ProductRepository.find().populate('store', { select: ['id'], @@ -2193,12 +1807,12 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name'), - store: _.pick(store1, 'id'), + ...product1, + store: store1Result, }, { - ..._.pick(product2, 'id', 'name'), - store: _.pick(store2, 'id'), + ...product2, + store: store2Result, }, ]); @@ -2210,10 +1824,12 @@ describe('ReadonlyRepository', () => { storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating a single relation when column is missing from partial select', async () => { - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - getQueryResult([_.pick(store1, 'id'), _.pick(store2, 'id')]), - ); + const product1Result = _.pick(product1, 'id', 'name', 'store'); + const product2Result = _.pick(product2, 'id', 'name', 'store'); + const store1Result = _.pick(store1, 'id'); + const store2Result = _.pick(store2, 'id'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1Result, product2Result]), getQueryResult([store1Result, store2Result])); const results = await ProductRepository.find({ select: ['name'], @@ -2223,12 +1839,12 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name'), - store: _.pick(store1, 'id'), + ...product1Result, + store: store1Result, }, { - ..._.pick(product2, 'id', 'name'), - store: _.pick(store2, 'id'), + ...product2Result, + store: store2Result, }, ]); @@ -2240,21 +1856,18 @@ describe('ReadonlyRepository', () => { storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating one-to-many collection', async () => { - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(store1, 'id', 'name'), _.pick(store2, 'id', 'name')]), - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store1, store2]), getQueryResult([product1, product3, product2])); const results = await StoreRepository.find().populate('products'); verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(store1, 'id', 'name'), - products: [_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')], + ...store1, + products: [product1, product3], }, { - ..._.pick(store2, 'id', 'name'), - products: [_.pick(product2, 'id', 'name', 'store')], + ...store2, + products: [product2], }, ]); results[0].products.length.should.equal(2); @@ -2269,10 +1882,8 @@ describe('ReadonlyRepository', () => { }); it('should support populating one-to-many collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); - when(poolOverride.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(store1, 'id', 'name'), _.pick(store2, 'id', 'name')]), - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - ); + + when(poolOverride.query(anyString(), anything())).thenResolve(getQueryResult([store1, store2]), getQueryResult([product1, product3, product2])); const results = await StoreRepository.find({ pool: instance(poolOverride), @@ -2282,12 +1893,12 @@ describe('ReadonlyRepository', () => { verify(poolOverride.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(store1, 'id', 'name'), - products: [_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')], + ...store1, + products: [product1, product3], }, { - ..._.pick(store2, 'id', 'name'), - products: [_.pick(product2, 'id', 'name', 'store')], + ...store2, + products: [product2], }, ]); results[0].products.length.should.equal(2); @@ -2302,10 +1913,9 @@ describe('ReadonlyRepository', () => { }); it('should support populating one-to-many collection with explicit pool override', async () => { const productPool = mock(Pool); - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([_.pick(store1, 'id', 'name'), _.pick(store2, 'id', 'name')])); - when(productPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - ); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store1, store2])); + when(productPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3, product2])); const results = await StoreRepository.find().populate('products', { pool: instance(productPool), @@ -2314,12 +1924,12 @@ describe('ReadonlyRepository', () => { verify(productPool.query(anyString(), anything())).once(); results.should.deep.equal([ { - ..._.pick(store1, 'id', 'name'), - products: [_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')], + ...store1, + products: [product1, product3], }, { - ..._.pick(store2, 'id', 'name'), - products: [_.pick(product2, 'id', 'name', 'store')], + ...store2, + products: [product2], }, ]); results[0].products.length.should.equal(2); @@ -2333,10 +1943,11 @@ describe('ReadonlyRepository', () => { storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating one-to-many collection with partial select and sort', async () => { - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(store1, 'id', 'name'), _.pick(store2, 'id', 'name')]), - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - ); + const product1Result = _.pick(product1, 'id', 'name', 'sku', 'store'); + const product2Result = _.pick(product2, 'id', 'name', 'sku', 'store'); + const product3Result = _.pick(product3, 'id', 'name', 'sku', 'store'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store1, store2]), getQueryResult([product1Result, product3Result, product2Result])); const results = await StoreRepository.find().populate('products', { select: ['name', 'sku', 'store'], @@ -2345,12 +1956,12 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(store1, 'id', 'name'), - products: [_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store')], + ...store1, + products: [product1Result, product3Result], }, { - ..._.pick(store2, 'id', 'name'), - products: [_.pick(product2, 'id', 'name', 'store')], + ...store2, + products: [product2Result], }, ]); results[0].products.length.should.equal(2); @@ -2365,24 +1976,24 @@ describe('ReadonlyRepository', () => { }); it('should support populating multi-multi collection', async () => { when(mockedPool.query(anyString(), anything())) - .thenResolve(getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')])) + .thenResolve(getQueryResult([product1, product3, product2])) .thenResolve(getQueryResult([product1Category1, product1Category2, product2Category1, product3Category1])) - .thenResolve(getQueryResult([_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')])); + .thenResolve(getQueryResult([category1, category2])); const results = await ProductRepository.find().populate('categories'); verify(mockedPool.query(anyString(), anything())).thrice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')], + ...product1, + categories: [category1, category2], }, { - ..._.pick(product3, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name')], + ...product3, + categories: [category1], }, { - ..._.pick(product2, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name')], + ...product2, + categories: [category1], }, ]); results[0].categories.length.should.equal(2); @@ -2401,9 +2012,9 @@ describe('ReadonlyRepository', () => { it('should support populating multi-multi collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); when(poolOverride.query(anyString(), anything())) - .thenResolve(getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')])) + .thenResolve(getQueryResult([product1, product3, product2])) .thenResolve(getQueryResult([product1Category1, product1Category2, product2Category1, product3Category1])) - .thenResolve(getQueryResult([_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')])); + .thenResolve(getQueryResult([category1, category2])); const results = await ProductRepository.find({ pool: instance(poolOverride), @@ -2413,16 +2024,16 @@ describe('ReadonlyRepository', () => { verify(poolOverride.query(anyString(), anything())).thrice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')], + ...product1, + categories: [category1, category2], }, { - ..._.pick(product3, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name')], + ...product3, + categories: [category1], }, { - ..._.pick(product2, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name')], + ...product2, + categories: [category1], }, ]); results[0].categories.length.should.equal(2); @@ -2440,12 +2051,10 @@ describe('ReadonlyRepository', () => { }); it('should support populating multi-multi collection with explicit pool override', async () => { const productPool = mock(Pool); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')]), - ); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3, product2])); when(productPool.query(anyString(), anything())) .thenResolve(getQueryResult([product1Category1, product1Category2, product2Category1, product3Category1])) - .thenResolve(getQueryResult([_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')])); + .thenResolve(getQueryResult([category1, category2])); const results = await ProductRepository.find().populate('categories', { pool: instance(productPool), @@ -2455,16 +2064,16 @@ describe('ReadonlyRepository', () => { verify(productPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')], + ...product1, + categories: [category1, category2], }, { - ..._.pick(product3, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name')], + ...product3, + categories: [category1], }, { - ..._.pick(product2, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id', 'name')], + ...product2, + categories: [category1], }, ]); results[0].categories.length.should.equal(2); @@ -2481,10 +2090,13 @@ describe('ReadonlyRepository', () => { categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with partial select and sort', async () => { + const category1Result = _.pick(category1, 'id'); + const category2Result = _.pick(category2, 'id'); + when(mockedPool.query(anyString(), anything())) - .thenResolve(getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')])) + .thenResolve(getQueryResult([product1, product3, product2])) .thenResolve(getQueryResult([product1Category1, product1Category2, product2Category1, product3Category1])) - .thenResolve(getQueryResult([_.pick(category1, 'id'), _.pick(category2, 'id')])); + .thenResolve(getQueryResult([category1Result, category2Result])); const results = await ProductRepository.find().populate('categories', { select: ['id'], @@ -2493,16 +2105,16 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).thrice(); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id'), _.pick(category2, 'id')], + ...product1, + categories: [category1Result, category2Result], }, { - ..._.pick(product3, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id')], + ...product3, + categories: [category1Result], }, { - ..._.pick(product2, 'id', 'name', 'store'), - categories: [_.pick(category1, 'id')], + ...product2, + categories: [category1Result], }, ]); results[0].categories.length.should.equal(2); @@ -2520,33 +2132,33 @@ describe('ReadonlyRepository', () => { }); it('should support populating multiple properties', async () => { when(mockedPool.query(anyString(), anything())) - .thenResolve(getQueryResult([_.pick(product1, 'id', 'name', 'store'), _.pick(product3, 'id', 'name', 'store'), _.pick(product2, 'id', 'name', 'store')])) + .thenResolve(getQueryResult([product1, product3, product2])) .thenResolve( getQueryResult([ // NOTE: Swapping the order to make sure that order doesn't matter - _.pick(store2, 'id', 'name'), - _.pick(store1, 'id', 'name'), + store2, + store1, ]), ) .thenResolve(getQueryResult([product1Category1, product1Category2, product2Category1, product3Category1])) - .thenResolve(getQueryResult([_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')])); + .thenResolve(getQueryResult([category1, category2])); const results = await ProductRepository.find().populate('store').populate('categories'); results.should.deep.equal([ { - ..._.pick(product1, 'id', 'name', 'store'), + ...product1, store: store1, - categories: [_.pick(category1, 'id', 'name'), _.pick(category2, 'id', 'name')], + categories: [category1, category2], }, { - ..._.pick(product3, 'id', 'name', 'store'), + ...product3, store: store1, - categories: [_.pick(category1, 'id', 'name')], + categories: [category1], }, { - ..._.pick(product2, 'id', 'name', 'store'), + ...product2, store: store2, - categories: [_.pick(category1, 'id', 'name')], + categories: [category1], }, ]); verify(mockedPool.query(anyString(), anything())).times(4); @@ -2570,11 +2182,14 @@ describe('ReadonlyRepository', () => { results[0].store.id.should.equal(store1.id); }); it('should support populating multiple properties with partial select and sort', async () => { + const parkingSpaceResult = _.pick(parkingSpace, 'id', 'name'); + const classroomResult = _.pick(classroom, 'id', 'name'); + when(mockedPool.query(anyString(), anything())) - .thenResolve(getQueryResult([_.pick(teacher1, 'id', 'firstName', 'lastName', 'isActive', 'parkingSpace'), _.pick(teacher2, 'id', 'firstName', 'lastName', 'isActive', 'parkingSpace')])) - .thenResolve(getQueryResult([_.pick(parkingSpace, 'id', 'name')])) + .thenResolve(getQueryResult([teacher1, teacher2])) + .thenResolve(getQueryResult([parkingSpaceResult])) .thenResolve(getQueryResult([teacher1Classroom])) - .thenResolve(getQueryResult([_.pick(classroom, 'id', 'name')])); + .thenResolve(getQueryResult([classroomResult])); async function getTeachers(): Promise< (Omit, 'parkingSpace'> & { @@ -2603,12 +2218,12 @@ describe('ReadonlyRepository', () => { const results = await getTeachers(); results.should.deep.equal([ { - ..._.pick(teacher1, 'id', 'firstName', 'lastName', 'isActive'), - parkingSpace: _.pick(parkingSpace, 'id', 'name'), - classrooms: [_.pick(classroom, 'id', 'name')], + ...teacher1, + parkingSpace: parkingSpaceResult, + classrooms: [parkingSpaceResult], }, { - ..._.pick(teacher2, 'id', 'firstName', 'lastName', 'isActive'), + teacher2, parkingSpace: undefined, classrooms: [], }, @@ -2634,10 +2249,10 @@ describe('ReadonlyRepository', () => { categoryQueryParams!.should.deep.equal([classroom.id, 'classroom%']); }); it('should support populating self reference', 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')]), - ); + const source1Result = _.pick(source1, 'id', 'name'); + const source2Result = _.pick(source2, 'id', 'name'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([source1Result, source2Result]), getQueryResult([translation1, translation2])); const results = await SimpleWithSelfReferenceRepository.find({ select: ['name'], @@ -2649,11 +2264,11 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { - ..._.pick(source1, 'id', 'name'), - translations: [_.pick(translation1, 'id', 'name', 'source'), _.pick(translation2, 'id', 'name', 'source')], + ...source1Result, + translations: [translation1, translation2], }, { - ..._.pick(source2, 'id', 'name'), + ...source2Result, translations: [], }, ]); @@ -2668,10 +2283,12 @@ describe('ReadonlyRepository', () => { translationsQueryParams!.should.deep.equal([[source1.id, source2.id]]); }); 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')]), - ); + const source1Result = _.pick(source1, 'id', 'name'); + const source2Result = _.pick(source2, 'id', 'name'); + const translation1Result = _.pick(translation1, 'id', 'name', 'source'); + const translation2Result = _.pick(translation2, 'id', 'name', 'source'); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([source1Result, source2Result]), getQueryResult([translation1Result, translation2Result])); try { await SimpleWithSelfReferenceRepository.find({ @@ -2695,16 +2312,19 @@ describe('ReadonlyRepository', () => { }); }); describe('#count()', () => { + let store: QueryResult; + beforeEach(() => { + store = generator.store(); + }); + it('should support call without constraints', async () => { const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve( @@ -2724,19 +2344,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([]); }); it('should support call constraints as a parameter', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve( @@ -2759,19 +2373,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([_.map(products, 'id'), store.id]); }); it('should support call with chained where constraints', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve( @@ -2793,19 +2401,13 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([store.id]); }); it('should support call with chained where constraints - Promise.all', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve( diff --git a/tests/utils/generator.ts b/tests/utils/generator.ts new file mode 100644 index 0000000..d832a2b --- /dev/null +++ b/tests/utils/generator.ts @@ -0,0 +1,178 @@ +import * as faker from 'faker'; + +import type { NotEntity, QueryResult } from '../../src'; +import type { IJsonLikeEntity, ProductCategory, SimpleWithRelationAndJson, TeacherClassroom } from '../models'; +import { + Category, + Classroom, + LevelOne, + LevelThree, + LevelTwo, + ParkingLot, + ParkingSpace, + Product, + SimpleWithJson, + SimpleWithSelfReference, + SimpleWithStringCollection, + SimpleWithStringId, + SimpleWithUnion, + Store, + Teacher, +} from '../models'; + +export function store(args?: Partial>): QueryResult { + const item = new Store(); + item.id = faker.datatype.number(); + item.name = `Store - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function product(args: Partial> & Pick, 'store'>): QueryResult { + const item = new Product(); + item.id = faker.datatype.number(); + item.name = `Product - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function category(args?: Partial>): QueryResult { + const item = new Category(); + item.id = faker.datatype.number(); + item.name = `Category - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function productCategory(productInput: QueryResult | number, categoryInput: QueryResult | number): QueryResult { + return { + id: faker.datatype.number(), + product: (productInput as QueryResult).id || (productInput as number), + category: (categoryInput as QueryResult).id || (categoryInput as number), + }; +} + +export function teacher(args?: Partial>): QueryResult { + const item = new Teacher(); + item.id = `Teacher - ${faker.datatype.uuid()}`; + item.firstName = faker.name.firstName(); + item.lastName = faker.name.lastName(); + item.isActive = true; + + return Object.assign(item, args); +} + +export function classroom(args?: Partial>): QueryResult { + const item = new Classroom(); + item.id = `Classroom - ${faker.datatype.uuid()}`; + item.name = `Classroom - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function teacherClassroom(teacherInput: QueryResult | string, classroomInput: QueryResult | string): QueryResult { + return { + id: `TeacherClassroom - ${faker.datatype.uuid()}`, + teacher: (teacherInput as QueryResult).id || (teacherInput as string), + classroom: (classroomInput as QueryResult).id || (classroomInput as string), + }; +} + +export function parkingLot(args?: Partial>): QueryResult { + const item = new ParkingLot(); + item.id = `ParkingLot - ${faker.datatype.uuid()}`; + item.name = `ParkingLot - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function parkingSpace(args: Partial> & Pick, 'parkingLot'>): QueryResult { + const item = new ParkingSpace(); + item.id = `ParkingSpace - ${faker.datatype.uuid()}`; + item.name = `ParkingSpace - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function levelOne(args: Partial> & Pick, 'levelTwo'>): QueryResult { + const item = new LevelOne(); + item.id = `LevelOne - ${faker.datatype.uuid()}`; + item.one = `One - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function levelTwo(args: Partial> & Pick, 'levelThree'>): QueryResult { + const item = new LevelTwo(); + item.id = `LevelTwo - ${faker.datatype.uuid()}`; + item.two = `Two - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function levelThree(args?: Partial>): QueryResult { + const item = new LevelThree(); + item.id = `LevelThree - ${faker.datatype.uuid()}`; + item.three = `Three - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function simpleWithSelfReference(args?: Partial>): QueryResult { + const item = new SimpleWithSelfReference(); + item.id = `SimpleWithSelfReference - ${faker.datatype.uuid()}`; + item.name = `WithSelfReference - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function simpleWithStringId(args?: Partial>): QueryResult { + const item = new SimpleWithStringId(); + item.id = `SimpleWithStringId - ${faker.datatype.uuid()}`; + item.name = `WithStringId - ${faker.datatype.uuid()}`; + + return Object.assign(item, args); +} + +export function simpleWithStringCollection(args?: Partial>): QueryResult { + const item = new SimpleWithStringCollection(); + item.id = faker.datatype.number(); + item.name = `WithStringCollection - ${faker.datatype.uuid()}`; + item.otherIds = [faker.datatype.uuid(), faker.datatype.uuid()]; + + return Object.assign(item, args); +} + +export function simpleWithUnion(args?: Partial>): QueryResult { + const item = new SimpleWithUnion(); + item.id = faker.datatype.number(); + item.name = `WithUnion - ${faker.datatype.uuid()}`; + item.status = 'Foobar'; + + return Object.assign(item, args); +} + +export function simpleWithJson(args?: Partial>): QueryResult { + const item = new SimpleWithJson(); + item.id = faker.datatype.number(); + item.name = `WithJson - ${faker.datatype.uuid()}`; + item.keyValue = { + foo: 42, + }; + + return Object.assign(item, args); +} + +export function simpleWithRelationAndJson( + args: Partial> & Pick, 'store'>, +): QueryResult & Required> { + return { + id: faker.datatype.number(), + name: `WithRelationAndJson - ${faker.datatype.uuid()}`, + message: { + id: 'foo', + message: 'bar', + } as NotEntity, + ...args, + }; +} From da80853165edf77795962b12b2a96ae4c2a74d2a Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 14:24:48 -0600 Subject: [PATCH 09/20] Remove null from TReturn Null is included as part of FindOneResult so it's not needed as the TReturn type --- src/IReadonlyRepository.ts | 2 +- src/ReadonlyRepository.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IReadonlyRepository.ts b/src/IReadonlyRepository.ts index dce4e4e..532161c 100644 --- a/src/IReadonlyRepository.ts +++ b/src/IReadonlyRepository.ts @@ -17,7 +17,7 @@ export interface IReadonlyRepository { // Optional keys specified as args.select K extends string & keyof T, // Return type used to pass through to all chained methods - TReturn = QueryResult> | null, + TReturn = QueryResult>, >( args: FindOneArgs | WhereQuery, ): FindOneResult; diff --git a/src/ReadonlyRepository.ts b/src/ReadonlyRepository.ts index f9c44ae..a27e87e 100644 --- a/src/ReadonlyRepository.ts +++ b/src/ReadonlyRepository.ts @@ -71,7 +71,7 @@ export class ReadonlyRepository implements IReadonlyRepository * @param {object} [args.where] - Object representing the where query * @param {string|object} [args.sort] - Property name(s) to sort by */ - public findOne | 'id'>> | null>(args: FindOneArgs | WhereQuery = {}): FindOneResult { + public findOne | 'id'>>>(args: FindOneArgs | WhereQuery = {}): FindOneResult { const { stack } = new Error(`${this.model.name}.findOne()`); let select: Set | undefined; From 48e1b1f0ba02a92624cd03760331c456c2714222 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 14:34:49 -0600 Subject: [PATCH 10/20] FIx tests based on moving to generators --- tests/readonlyRepository.tests.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index 2ec54f0..ff08ebc 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -149,7 +149,7 @@ describe('ReadonlyRepository', () => { it('should support call with sort as a parameter', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); - const result = await ProductRepository.findOne({ + const result = await ReadonlyProductRepository.findOne({ sort: 'name', }); assert(result); @@ -157,8 +157,8 @@ describe('ReadonlyRepository', () => { result.name.should.equal(product.name); const [query, params] = capture(mockedPool.query).first(); - query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" LIMIT 1 ORDER BY "name"'); - params!.should.deep.equal([product.id]); + query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" ORDER BY "name" LIMIT 1'); + params!.should.deep.equal([]); }); it('should support call with where constraint as a parameter', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -485,7 +485,10 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); verify(poolOverride.query(anyString(), anything())).twice(); assert(result); - result.should.deep.equal(product); + result.should.deep.equal({ + ...product, + store, + }); const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); @@ -1625,7 +1628,7 @@ describe('ReadonlyRepository', () => { product1Category1 = generator.productCategory(product1.id, category1.id); product1Category2 = generator.productCategory(product1.id, category2.id); product2Category1 = generator.productCategory(product2, category1); - product3Category1 = generator.productCategory(product2, category1); + product3Category1 = generator.productCategory(product3, category1); parkingLot = generator.parkingLot(); parkingSpace = generator.parkingSpace({ @@ -1661,18 +1664,18 @@ describe('ReadonlyRepository', () => { }); it('should support populating a single relation - same/shared', async () => { - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3]), getQueryResult([store])); + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product1, product3]), getQueryResult([store1])); const results = await ProductRepository.find().populate('store'); verify(mockedPool.query(anyString(), anything())).twice(); results.should.deep.equal([ { ...product1, - store, + store: store1, }, { ...product3, - store, + store: store1, }, ]); @@ -2220,10 +2223,10 @@ describe('ReadonlyRepository', () => { { ...teacher1, parkingSpace: parkingSpaceResult, - classrooms: [parkingSpaceResult], + classrooms: [classroomResult], }, { - teacher2, + ...teacher2, parkingSpace: undefined, classrooms: [], }, From 279432975faba73a3644961042d7d48056e34f2d Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 14:45:27 -0600 Subject: [PATCH 11/20] Update npms * Update node minimum to version 14 --- README.md | 2 +- package-lock.json | 194 +++++++++++++++++++++++----------------------- package.json | 20 ++--- tsconfig.json | 2 +- 4 files changed, 110 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 44a6821..d7e4031 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This ORM does not: ## Compatibility -- Node.js v12 or above +- Node.js v14 or above - [PostgreSQL](http://www.postgresql.org/) 12 or above. Lower versions _should_ work. ## Install diff --git a/package-lock.json b/package-lock.json index cd905a7..23e186f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "9.2.4", + "version": "10.0.0-beta1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20,14 +20,14 @@ } }, "@es-joy/jsdoccomment": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.13.0.tgz", - "integrity": "sha512-APVqbVPGOprb4BmjEnwbSzV+V2e/6DVIUnZG3zdW5uWXWkN0DKMCpiIy2TdBauoANKYO7RQpO8cTjIYNVSKwUA==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.14.2.tgz", + "integrity": "sha512-812igKXDcLEdkwUbJvnhzMy88dBBiDeaf3mMF1jnQwclIObu5UQB8ow1KAvDRN1FQqpB+IsZnpmRA0jZ6KGt3g==", "dev": true, "requires": { "comment-parser": "1.3.0", "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "2.0.0" + "jsdoc-type-pratt-parser": "2.0.2" } }, "@eslint/eslintrc": { @@ -178,13 +178,14 @@ "integrity": "sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==" }, "@typescript-eslint/eslint-plugin": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz", - "integrity": "sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz", + "integrity": "sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.8.1", - "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/experimental-utils": "5.9.0", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/type-utils": "5.9.0", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", @@ -194,55 +195,66 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz", - "integrity": "sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz", + "integrity": "sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.8.1", - "@typescript-eslint/types": "5.8.1", - "@typescript-eslint/typescript-estree": "5.8.1", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/parser": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.8.1.tgz", - "integrity": "sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.0.tgz", + "integrity": "sha512-/6pOPz8yAxEt4PLzgbFRDpZmHnXCeZgPDrh/1DaVKOjvn/UPMlWhbx/gA96xRi2JxY1kBl2AmwVbyROUqys5xQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.8.1", - "@typescript-eslint/types": "5.8.1", - "@typescript-eslint/typescript-estree": "5.8.1", + "@typescript-eslint/scope-manager": "5.9.0", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/typescript-estree": "5.9.0", "debug": "^4.3.2" } }, "@typescript-eslint/scope-manager": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz", - "integrity": "sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz", + "integrity": "sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.8.1", - "@typescript-eslint/visitor-keys": "5.8.1" + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz", + "integrity": "sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "5.9.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.8.1.tgz", - "integrity": "sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz", + "integrity": "sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz", - "integrity": "sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz", + "integrity": "sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.8.1", - "@typescript-eslint/visitor-keys": "5.8.1", + "@typescript-eslint/types": "5.9.0", + "@typescript-eslint/visitor-keys": "5.9.0", "debug": "^4.3.2", "globby": "^11.0.4", "is-glob": "^4.0.3", @@ -251,12 +263,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz", - "integrity": "sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz", + "integrity": "sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/types": "5.9.0", "eslint-visitor-keys": "^3.0.0" } }, @@ -433,9 +445,9 @@ } }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true } } @@ -511,9 +523,9 @@ } }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true } } @@ -966,9 +978,9 @@ "dev": true }, "eslint": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.5.0.tgz", - "integrity": "sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz", + "integrity": "sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==", "dev": true, "requires": { "@eslint/eslintrc": "^1.0.5", @@ -983,7 +995,7 @@ "eslint-scope": "^7.1.0", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.1.0", - "espree": "^9.2.0", + "espree": "^9.3.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1101,14 +1113,13 @@ } }, "eslint-module-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", - "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz", + "integrity": "sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==", "dev": true, "requires": { "debug": "^3.2.7", - "find-up": "^2.1.0", - "pkg-dir": "^2.0.0" + "find-up": "^2.1.0" }, "dependencies": { "debug": { @@ -1123,9 +1134,9 @@ } }, "eslint-plugin-import": { - "version": "2.25.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", - "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, "requires": { "array-includes": "^3.1.4", @@ -1133,14 +1144,14 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.1", + "eslint-module-utils": "^2.7.2", "has": "^1.0.3", "is-core-module": "^2.8.0", "is-glob": "^4.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.5", "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" + "tsconfig-paths": "^3.12.0" }, "dependencies": { "debug": { @@ -1179,17 +1190,17 @@ } }, "eslint-plugin-jsdoc": { - "version": "37.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.4.0.tgz", - "integrity": "sha512-XWKMMHFq7eUdC8XMzuQSskevJvlHTDSAJm/2qtEZ7+qhZTZ0YjeqWaUn7KGdrmxLNqtWwtJ67LdIPgrYUZ5EoA==", + "version": "37.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-37.5.1.tgz", + "integrity": "sha512-WMv/Na5QdpMQao1MR3SgYpGFi2PSrhh/JljlErQru9ZYXf1j9oQVIVCELQV7jcyqKQ/svPqCyU8eMhet9dzP+w==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "0.13.0", + "@es-joy/jsdoccomment": "0.14.2", "comment-parser": "1.3.0", "debug": "^4.3.3", "escape-string-regexp": "^4.0.0", "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "^2.0.0", + "jsdoc-type-pratt-parser": "^2.0.2", "regextras": "^0.8.0", "semver": "^7.3.5", "spdx-expression-parse": "^3.0.1" @@ -1274,12 +1285,12 @@ "dev": true }, "espree": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz", - "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", + "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", "dev": true, "requires": { - "acorn": "^8.6.0", + "acorn": "^8.7.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^3.1.0" }, @@ -1882,9 +1893,9 @@ } }, "jsdoc-type-pratt-parser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.0.0.tgz", - "integrity": "sha512-sUuj2j48wxrEpbFjDp1sAesAxPiLT+z0SWVmMafyIINs6Lj5gIPKh3VrkBZu4E/Dv+wHpOot0m6H8zlHQjwqeQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.0.2.tgz", + "integrity": "sha512-gXN5CxeaI9WtYQYzpOO/CtTRfZppQlKxXRTIm73JuAX6kOGTQ7iZ0e+YB+b2m7Fk+gTYYxRtE1nqje7H6dqv8w==", "dev": true }, "json-parse-better-errors": { @@ -1946,9 +1957,9 @@ } }, "lint-staged": { - "version": "12.1.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.4.tgz", - "integrity": "sha512-RgDz9nsFsE0/5eL9Vat0AvCuk0+j5mEuzBIVfrRH5FRtt5wibYe8zTjZs2nuqLFrLAGQGYnj8+HJxolcj08i/A==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.5.tgz", + "integrity": "sha512-WyKb+0sNKDTd1LwwAfTBPp0XmdaKkAOEbg4oHE4Kq2+oQVchg/VAcjVQtSqZih1izNsTURjc2EkhG/syRQUXdA==", "dev": true, "requires": { "cli-truncate": "^3.1.0", @@ -1990,9 +2001,9 @@ } }, "listr2": { - "version": "3.13.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz", - "integrity": "sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", "dev": true, "requires": { "cli-truncate": "^2.1.0", @@ -2000,7 +2011,7 @@ "log-update": "^4.0.0", "p-map": "^4.0.0", "rfdc": "^1.3.0", - "rxjs": "^7.4.0", + "rxjs": "^7.5.1", "through": "^2.3.8", "wrap-ansi": "^7.0.0" }, @@ -2712,9 +2723,9 @@ } }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true } } @@ -2925,15 +2936,6 @@ "fromentries": "^1.3.2" } }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -3157,9 +3159,9 @@ } }, "rxjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.0.tgz", - "integrity": "sha512-fuCKAfFawVYX0pyFlETtYnXI+5iiY9Dftgk+VdgeOq+Qyi9ZDWckHZRDaXRt5WCNbbLkmAheoSGDiceyCIKNZA==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.1.tgz", + "integrity": "sha512-KExVEeZWxMZnZhUZtsJcFwz8IvPvgu4G2Z2QyqjZQzUGr32KDYuSxrEYO4w3tFFNbfLozcrKUTvTPi+E9ywJkQ==", "dev": true, "requires": { "tslib": "^2.1.0" @@ -3469,9 +3471,9 @@ } }, "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", "dev": true, "requires": { "@types/json5": "^0.0.29", diff --git a/package.json b/package.json index 8858a89..b20c650 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "9.2.4", + "version": "10.0.0-beta1", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", @@ -16,36 +16,36 @@ }, "homepage": "https://github.com/bigalorm/bigal#readme", "engines": { - "node": ">=12" + "node": ">=14" }, "dependencies": { "@types/lodash": "^4.14.178", - "@types/node": "^17.0.5", + "@types/node": ">=14.15", "@types/pg": "^8.6.3", "lodash": "^4.17.21", - "pg": "8.7.1", + "pg": "^8.7.1", "postgres-pool": "^5.0.9" }, "devDependencies": { "@types/chai": "^4.3.0", "@types/faker": "^5.5.9", "@types/mocha": "^9.0.0", - "@typescript-eslint/eslint-plugin": "^5.8.1", - "@typescript-eslint/parser": "^5.8.1", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", "chai": "^4.3.4", - "eslint": "^8.5.0", + "eslint": "^8.6.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^16.1.0", "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsdoc": "^37.4.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jsdoc": "^37.5.1", "eslint-plugin-mocha": "^10.0.3", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-security": "^1.4.0", "faker": "^5.5.3", "husky": "^7.0.4", - "lint-staged": "^12.1.4", + "lint-staged": "^12.1.5", "markdownlint-cli": "^0.30.0", "mocha": "^9.1.3", "npm-run-all": "^4.1.5", diff --git a/tsconfig.json b/tsconfig.json index fd7cae7..db23b20 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2018", + "target": "es2020", "module": "commonjs", "lib": ["esnext"], "moduleResolution": "node", From 07bd3d418771f3b610b8dedef52557e22eb1c822 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 17:28:17 -0600 Subject: [PATCH 12/20] Remove non-null assertion --- tests/readonlyRepository.tests.ts | 360 ++++++++++++++------- tests/repository.tests.ts | 514 +++++++++++++++--------------- tests/sqlHelper.tests.ts | 226 ++++++------- 3 files changed, 605 insertions(+), 495 deletions(-) diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index ff08ebc..83cce4c 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -126,7 +126,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" LIMIT 1'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with constraints as a parameter', async () => { const productResult = _.pick(product, 'id', 'name'); @@ -144,7 +145,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "name","id" FROM "products" WHERE "id"=$1 ORDER BY "name" LIMIT 1'); - params!.should.deep.equal([product.id]); + assert(params); + params.should.deep.equal([product.id]); }); it('should support call with sort as a parameter', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -158,7 +160,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" ORDER BY "name" LIMIT 1'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with where constraint as a parameter', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -171,7 +174,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - params!.should.deep.equal([product.id]); + assert(params); + params.should.deep.equal([product.id]); }); it('should support call with where constraint as a parameter and querying id by entity value', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -184,7 +188,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - params!.should.deep.equal([product.id]); + assert(params); + params.should.deep.equal([product.id]); }); it('should support call with where constraint as a parameter and querying property by entity value', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -197,7 +202,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 LIMIT 1'); - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with explicit pool override', async () => { const poolOverride = mock(Pool); @@ -215,7 +221,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); const [query, params] = capture(poolOverride.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - params!.should.deep.equal([product.id]); + assert(params); + params.should.deep.equal([product.id]); }); it('should support call with chained where constraints', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -228,7 +235,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - params!.should.deep.equal([product.id]); + assert(params); + params.should.deep.equal([product.id]); }); it('should support call with chained where constraints - Promise.all', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -243,7 +251,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - params!.should.deep.equal([product.id]); + assert(params); + params.should.deep.equal([product.id]); }); it('should support call with chained sort', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -254,7 +263,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" ORDER BY "name" LIMIT 1'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); describe('Parse number columns', () => { it('should parse integer columns from integer query value', async () => { @@ -284,7 +294,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should parse integer columns from float strings query value', async () => { const id = faker.datatype.number(); @@ -312,7 +323,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should parse integer columns that return as number', async () => { const id = faker.datatype.number(); @@ -340,7 +352,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should ignore large integer columns values', async () => { const id = faker.datatype.number(); @@ -368,7 +381,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should parse float columns return as float strings', async () => { const id = faker.datatype.number(); @@ -396,7 +410,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should parse float columns return as number', async () => { const id = faker.datatype.number(); @@ -424,7 +439,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should ignore large float columns', async () => { const id = faker.datatype.number(); @@ -452,7 +468,8 @@ describe('ReadonlyRepository', () => { query.should.equal( `SELECT "id","name","int_column" AS "intColumn","float_column" AS "floatColumn","array_column" AS "arrayColumn","string_array_column" AS "stringArrayColumn" FROM "${ReadonlyKitchenSinkRepository.model.tableName}" LIMIT 1`, ); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); }); it('should support populating a single relation', async () => { @@ -468,10 +485,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store.id]); }); it('should support populating a single relation with implicit inherited pool override', async () => { const poolOverride = mock(Pool); @@ -492,10 +511,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(poolOverride.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store.id]); }); it('should support populating a single relation with explicit pool override', async () => { const storePool = mock(Pool); @@ -516,10 +537,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(storePool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store.id]); }); it('should support populating a single relation when column is missing from partial select', async () => { const productResult = _.pick(product, 'id', 'name', 'store'); @@ -540,10 +563,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "name","store_id" AS "store","id" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "name","id" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store.id]); }); it('should support populating a single relation as QueryResult with partial select', async () => { const levelThreeItem = generator.levelThree(); @@ -588,10 +613,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "name","id" FROM "stores" WHERE "id"=$1 ORDER BY "name"'); - storeQueryParams!.should.deep.equal([store.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store.id]); }); it('should support populating collection', async () => { const product1 = generator.product({ @@ -622,10 +649,12 @@ describe('ReadonlyRepository', () => { const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); - storeQueryParams!.should.deep.equal([]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([]); const [productQuery, productQueryParams] = capture(mockedPool.query).second(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - productQueryParams!.should.deep.equal([store.id]); + assert(productQueryParams); + productQueryParams.should.deep.equal([store.id]); }); it('should support populating collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); @@ -653,10 +682,12 @@ describe('ReadonlyRepository', () => { const [storeQuery, storeQueryParams] = capture(poolOverride.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); - storeQueryParams!.should.deep.equal([]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([]); const [productQuery, productQueryParams] = capture(poolOverride.query).second(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - productQueryParams!.should.deep.equal([store.id]); + assert(productQueryParams); + productQueryParams.should.deep.equal([store.id]); }); it('should support populating collection with explicit pool override', async () => { const productPool = mock(Pool); @@ -683,10 +714,12 @@ describe('ReadonlyRepository', () => { const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); - storeQueryParams!.should.deep.equal([]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([]); const [productQuery, productQueryParams] = capture(productPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - productQueryParams!.should.deep.equal([store.id]); + assert(productQueryParams); + productQueryParams.should.deep.equal([store.id]); }); it('should support populating collection with partial select and order', async () => { const product1 = generator.product({ @@ -714,10 +747,12 @@ describe('ReadonlyRepository', () => { const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); - storeQueryParams!.should.deep.equal([]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([]); const [productQuery, productQueryParams] = capture(mockedPool.query).second(); productQuery.should.equal('SELECT "name","id" FROM "products" WHERE "store_id"=$1 ORDER BY "alias_names"'); - productQueryParams!.should.deep.equal([store.id]); + assert(productQueryParams); + productQueryParams.should.deep.equal([store.id]); }); it('should support populating multi-multi collection', async () => { const category1 = generator.category(); @@ -737,13 +772,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryMapQuery, productCategoryMapQueryParams] = capture(mockedPool.query).second(); productCategoryMapQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); - productCategoryMapQueryParams!.should.deep.equal([product.id]); + assert(productCategoryMapQueryParams); + productCategoryMapQueryParams.should.deep.equal([product.id]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).third(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); @@ -768,13 +806,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryMapQuery, productCategoryMapQueryParams] = capture(poolOverride.query).second(); productCategoryMapQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); - productCategoryMapQueryParams!.should.deep.equal([product.id]); + assert(productCategoryMapQueryParams); + productCategoryMapQueryParams.should.deep.equal([product.id]); const [categoryQuery, categoryQueryParams] = capture(poolOverride.query).third(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with explicit pool override', async () => { const categoryPool = mock(Pool); @@ -801,13 +842,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryMapQuery, productCategoryMapQueryParams] = capture(categoryPool.query).first(); productCategoryMapQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); - productCategoryMapQueryParams!.should.deep.equal([product.id]); + assert(productCategoryMapQueryParams); + productCategoryMapQueryParams.should.deep.equal([product.id]); const [categoryQuery, categoryQueryParams] = capture(categoryPool.query).second(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with partial select and order', async () => { const category1 = generator.category(); @@ -837,13 +881,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryMapQuery, productCategoryMapQueryParams] = capture(mockedPool.query).second(); productCategoryMapQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); - productCategoryMapQueryParams!.should.deep.equal([product.id]); + assert(productCategoryMapQueryParams); + productCategoryMapQueryParams.should.deep.equal([product.id]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).third(); categoryQuery.should.equal('SELECT "name","id" FROM "categories" WHERE "id"=ANY($1::INTEGER[]) ORDER BY "name" DESC'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating self reference collection', async () => { const source1 = generator.simpleWithSelfReference(); @@ -878,10 +925,12 @@ describe('ReadonlyRepository', () => { const [sourceQuery, sourceQueryParams] = capture(mockedPool.query).first(); sourceQuery.should.equal('SELECT "name","id" FROM "simple" WHERE "id"=$1 LIMIT 1'); - sourceQueryParams!.should.deep.equal([source1.id]); + assert(sourceQueryParams); + sourceQueryParams.should.deep.equal([source1.id]); const [translationsQuery, translationsQueryParams] = capture(mockedPool.query).second(); translationsQuery.should.equal('SELECT "id","name","source_id" AS "source" FROM "simple" WHERE "source_id"=$1'); - translationsQueryParams!.should.deep.equal([source1.id]); + assert(translationsQueryParams); + translationsQueryParams.should.deep.equal([source1.id]); }); it('should support populating collection and not explicitly selecting relation column', async () => { const source1 = generator.simpleWithSelfReference(); @@ -920,10 +969,12 @@ describe('ReadonlyRepository', () => { const [sourceQuery, sourceQueryParams] = capture(mockedPool.query).first(); sourceQuery.should.equal('SELECT "name","id" FROM "simple" WHERE "id"=$1 LIMIT 1'); - sourceQueryParams!.should.deep.equal([source1.id]); + assert(sourceQueryParams); + sourceQueryParams.should.deep.equal([source1.id]); const [translationsQuery, translationsQueryParams] = capture(mockedPool.query).second(); translationsQuery.should.equal('SELECT "id","name" FROM "simple" WHERE "source_id"=$1'); - translationsQueryParams!.should.deep.equal([source1.id]); + assert(translationsQueryParams); + translationsQueryParams.should.deep.equal([source1.id]); }); it('should support complex query with multiple chained modifiers', async () => { const category1 = generator.category(); @@ -969,16 +1020,20 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 ORDER BY "store_id" DESC LIMIT 1'); - productQueryParams!.should.deep.equal([store.id]); + assert(productQueryParams); + productQueryParams.should.deep.equal([store.id]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1 AND "name" ILIKE $2'); - storeQueryParams!.should.deep.equal([store.id, 'store%']); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store.id, 'store%']); const [productCategoryMapQuery, productCategoryMapQueryParams] = capture(mockedPool.query).third(); productCategoryMapQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); - productCategoryMapQueryParams!.should.deep.equal([product.id]); + assert(productCategoryMapQueryParams); + productCategoryMapQueryParams.should.deep.equal([product.id]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).byCallIndex(3); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[]) AND "name" ILIKE $2 ORDER BY "name" LIMIT 2'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id], 'category%']); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id], 'category%']); }); it('should have instance functions be equal across multiple queries', async () => { const result = { @@ -1031,7 +1086,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","other_ids" AS "otherIds" FROM "simple" WHERE (("id"=$1) OR (($2=ANY("other_ids") OR $3=ANY("other_ids")))) LIMIT 1'); - params!.should.deep.equal([simple.id, otherSimple.id, anotherSimple.id]); + assert(params); + params.should.deep.equal([simple.id, otherSimple.id, anotherSimple.id]); }); it('should support an object with an enum/union field', async () => { const simple = generator.simpleWithUnion(); @@ -1045,7 +1101,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","status" FROM "simple" WHERE "status"=ANY($1::TEXT[]) LIMIT 1'); - params!.should.deep.equal([['Bar', 'Foo']]); + assert(params); + params.should.deep.equal([['Bar', 'Foo']]); }); it('should support an object with negated enum/union field', async () => { const simple = generator.simpleWithUnion(); @@ -1061,7 +1118,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","status" FROM "simple" WHERE "status"<>ALL($1::TEXT[]) LIMIT 1'); - params!.should.deep.equal([['Bar', 'Foo']]); + assert(params); + params.should.deep.equal([['Bar', 'Foo']]); }); it('should support an object with a json field', async () => { const simple = generator.simpleWithJson(); @@ -1074,7 +1132,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","bar","key_value" AS "keyValue" FROM "simple" LIMIT 1'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support an object with a json field (with id property)', async () => { const simple = generator.simpleWithRelationAndJson({ @@ -1098,7 +1157,8 @@ describe('ReadonlyRepository', () => { 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'); - params!.should.deep.equal([simple.name, simple.id, 42]); + assert(params); + params.should.deep.equal([simple.name, simple.id, 42]); }); it('should support an object with a json field (with id property) and populate statement', async () => { const simple = generator.simpleWithRelationAndJson({ @@ -1123,7 +1183,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","store_id" AS "store","message" FROM "simple" LIMIT 1'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support retaining original field - UNSAFE_withOriginalFieldType()', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( @@ -1190,7 +1251,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with constraints as a parameter', async () => { const products = [ @@ -1218,7 +1280,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "name","id" FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2 ORDER BY "name" LIMIT 24 OFFSET 5'); - params!.should.deep.equal([_.map(products, 'id'), store.id]); + assert(params); + params.should.deep.equal([_.map(products, 'id'), store.id]); }); it('should support call with where constraint as a parameter', async () => { const products = [ @@ -1240,7 +1303,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2'); - params!.should.deep.equal([_.map(products, 'id'), store.id]); + assert(params); + params.should.deep.equal([_.map(products, 'id'), store.id]); }); it('should support call with explicit pool override', async () => { const poolOverride = mock(Pool); @@ -1263,7 +1327,8 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).never(); const [query, params] = capture(poolOverride.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with chained where constraints', async () => { const products = [ @@ -1284,7 +1349,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with chained where constraints - array ILIKE array of values', async () => { const products = [ @@ -1321,7 +1387,8 @@ describe('ReadonlyRepository', () => { query.should.equal( 'SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE (("name" ILIKE $1) OR ("name" ILIKE $2)) AND EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE lower("unnested_alias_names")=ANY($3::TEXT[]))', ); - params!.should.deep.equal(['product', 'Foo Bar', ['foo', 'bar']]); + assert(params); + params.should.deep.equal(['product', 'Foo Bar', ['foo', 'bar']]); }); it('should support call with chained where constraints - NOT ILIKE array of values', async () => { const products = [ @@ -1348,7 +1415,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE lower("sku")<>ALL($1::TEXT[])'); - params!.should.deep.equal([['foo', 'bar']]); + assert(params); + params.should.deep.equal([['foo', 'bar']]); }); it('should support call with chained where constraints - Promise.all', async () => { const products = [ @@ -1371,7 +1439,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with chained sort', async () => { const products = [ @@ -1390,7 +1459,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" ORDER BY "name"'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with chained limit', async () => { const products = [ @@ -1409,7 +1479,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 42'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with chained skip', async () => { const products = [ @@ -1428,7 +1499,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" OFFSET 24'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call with chained paginate', async () => { const products = [ @@ -1450,7 +1522,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 100 OFFSET 200'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support complex query with multiple chained modifiers', async () => { const products = [ @@ -1478,7 +1551,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 ORDER BY "store_id" DESC LIMIT 42 OFFSET 24'); - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); it('should have instance functions be equal across multiple queries', async () => { const result = { @@ -1681,10 +1755,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store1.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store1.id]); }); it('should support populating a single relation - different', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( @@ -1711,10 +1787,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=ANY($1::INTEGER[])'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating a single relation with implicit inherited pool override', async () => { const poolOverride = mock(Pool); @@ -1740,10 +1818,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(poolOverride.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store1.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store1.id]); }); it('should support populating a single relation with explicit pool override', async () => { const storePool = mock(Pool); @@ -1770,10 +1850,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(storePool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1'); - storeQueryParams!.should.deep.equal([store1.id]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([store1.id]); }); it('should support populating a single relation as QueryResult with partial select', async () => { const levelOneResult = _.pick(levelOneItem, 'id', 'one', 'levelTwo'); @@ -1821,10 +1903,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id" FROM "stores" WHERE "id"=ANY($1::INTEGER[]) ORDER BY "name"'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating a single relation when column is missing from partial select', async () => { const product1Result = _.pick(product1, 'id', 'name', 'store'); @@ -1853,10 +1937,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "name","store_id" AS "store","id" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id" FROM "stores" WHERE "id"=ANY($1::INTEGER[])'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating one-to-many collection', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store1, store2]), getQueryResult([product1, product3, product2])); @@ -1878,10 +1964,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name" FROM "stores"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=ANY($1::INTEGER[])'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating one-to-many collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); @@ -1909,10 +1997,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name" FROM "stores"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(poolOverride.query).second(); storeQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=ANY($1::INTEGER[])'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating one-to-many collection with explicit pool override', async () => { const productPool = mock(Pool); @@ -1940,10 +2030,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name" FROM "stores"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(productPool.query).first(); storeQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=ANY($1::INTEGER[])'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating one-to-many collection with partial select and sort', async () => { const product1Result = _.pick(product1, 'id', 'name', 'sku', 'store'); @@ -1972,10 +2064,12 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name" FROM "stores"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "name","sku","store_id" AS "store","id" FROM "products" WHERE "store_id"=ANY($1::INTEGER[]) ORDER BY "name"'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); }); it('should support populating multi-multi collection', async () => { when(mockedPool.query(anyString(), anything())) @@ -2004,13 +2098,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryQuery, productCategoryQueryParams] = capture(mockedPool.query).second(); productCategoryQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=ANY($1::INTEGER[])'); - productCategoryQueryParams!.should.deep.equal([[product1.id, product3.id, product2.id]]); + assert(productCategoryQueryParams); + productCategoryQueryParams.should.deep.equal([[product1.id, product3.id, product2.id]]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).third(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with implicit inherited pool override', async () => { const poolOverride = mock(Pool); @@ -2044,13 +2141,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(poolOverride.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryQuery, productCategoryQueryParams] = capture(poolOverride.query).second(); productCategoryQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=ANY($1::INTEGER[])'); - productCategoryQueryParams!.should.deep.equal([[product1.id, product3.id, product2.id]]); + assert(productCategoryQueryParams); + productCategoryQueryParams.should.deep.equal([[product1.id, product3.id, product2.id]]); const [categoryQuery, categoryQueryParams] = capture(poolOverride.query).third(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with explicit pool override', async () => { const productPool = mock(Pool); @@ -2084,13 +2184,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryQuery, productCategoryQueryParams] = capture(productPool.query).first(); productCategoryQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=ANY($1::INTEGER[])'); - productCategoryQueryParams!.should.deep.equal([[product1.id, product3.id, product2.id]]); + assert(productCategoryQueryParams); + productCategoryQueryParams.should.deep.equal([[product1.id, product3.id, product2.id]]); const [categoryQuery, categoryQueryParams] = capture(productPool.query).second(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multi-multi collection with partial select and sort', async () => { const category1Result = _.pick(category1, 'id'); @@ -2125,13 +2228,16 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [productCategoryQuery, productCategoryQueryParams] = capture(mockedPool.query).second(); productCategoryQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=ANY($1::INTEGER[])'); - productCategoryQueryParams!.should.deep.equal([[product1.id, product3.id, product2.id]]); + assert(productCategoryQueryParams); + productCategoryQueryParams.should.deep.equal([[product1.id, product3.id, product2.id]]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).third(); categoryQuery.should.equal('SELECT "id" FROM "categories" WHERE "id"=ANY($1::INTEGER[]) ORDER BY "name"'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); }); it('should support populating multiple properties', async () => { when(mockedPool.query(anyString(), anything())) @@ -2171,16 +2277,20 @@ describe('ReadonlyRepository', () => { const [productQuery, productQueryParams] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - productQueryParams!.should.deep.equal([]); + assert(productQueryParams); + productQueryParams.should.deep.equal([]); const [storeQuery, storeQueryParams] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=ANY($1::INTEGER[])'); - storeQueryParams!.should.deep.equal([[store1.id, store2.id]]); + assert(storeQueryParams); + storeQueryParams.should.deep.equal([[store1.id, store2.id]]); const [productCategoryQuery, productCategoryQueryParams] = capture(mockedPool.query).third(); productCategoryQuery.should.equal('SELECT "product_id" AS "product","category_id" AS "category","id" FROM "product__category" WHERE "product_id"=ANY($1::INTEGER[])'); - productCategoryQueryParams!.should.deep.equal([[product1.id, product3.id, product2.id]]); + assert(productCategoryQueryParams); + productCategoryQueryParams.should.deep.equal([[product1.id, product3.id, product2.id]]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).last(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - categoryQueryParams!.should.deep.equal([[category1.id, category2.id]]); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([[category1.id, category2.id]]); results[0].store.id.should.equal(store1.id); }); @@ -2240,16 +2350,20 @@ describe('ReadonlyRepository', () => { teacherQuery.should.equal( 'SELECT "id","first_name" AS "firstName","last_name" AS "lastName","parking_space_id" AS "parkingSpace","is_active" AS "isActive" FROM "teacher" WHERE "is_active"=$1 ORDER BY "last_name"', ); - teacherQueryParams!.should.deep.equal([true]); + assert(teacherQueryParams); + teacherQueryParams.should.deep.equal([true]); const [parkingSpaceQuery, parkingSpaceQueryParams] = capture(mockedPool.query).second(); parkingSpaceQuery.should.equal('SELECT "name","id" FROM "parking_space" WHERE "id"=$1'); - parkingSpaceQueryParams!.should.deep.equal([parkingSpace.id]); + assert(parkingSpaceQueryParams); + parkingSpaceQueryParams.should.deep.equal([parkingSpace.id]); const [teacherClassroomQuery, teacherClassroomQueryParams] = capture(mockedPool.query).third(); teacherClassroomQuery.should.equal('SELECT "teacher_id" AS "teacher","classroom_id" AS "classroom","id" FROM "teacher__classroom" WHERE "teacher_id"=ANY($1::TEXT[])'); - teacherClassroomQueryParams!.should.deep.equal([[teacher1.id, teacher2.id]]); + assert(teacherClassroomQueryParams); + teacherClassroomQueryParams.should.deep.equal([[teacher1.id, teacher2.id]]); const [categoryQuery, categoryQueryParams] = capture(mockedPool.query).last(); categoryQuery.should.equal('SELECT "name","id" FROM "classroom" WHERE "id"=$1 AND "name" ILIKE $2'); - categoryQueryParams!.should.deep.equal([classroom.id, 'classroom%']); + assert(categoryQueryParams); + categoryQueryParams.should.deep.equal([classroom.id, 'classroom%']); }); it('should support populating self reference', async () => { const source1Result = _.pick(source1, 'id', 'name'); @@ -2280,10 +2394,12 @@ describe('ReadonlyRepository', () => { const [sourceQuery, sourceQueryParams] = capture(mockedPool.query).first(); sourceQuery.should.equal('SELECT "name","id" FROM "simple" WHERE "source_id" IS NULL'); - sourceQueryParams!.should.deep.equal([]); + assert(sourceQueryParams); + sourceQueryParams.should.deep.equal([]); const [translationsQuery, translationsQueryParams] = capture(mockedPool.query).second(); 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]]); + assert(translationsQueryParams); + translationsQueryParams.should.deep.equal([[source1.id, source2.id]]); }); it('should throw when attempting to populate collection and not not explicitly specifying relation column', async () => { const source1Result = _.pick(source1, 'id', 'name'); @@ -2344,7 +2460,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products"'); - params!.should.deep.equal([]); + assert(params); + params.should.deep.equal([]); }); it('should support call constraints as a parameter', async () => { const products = [ @@ -2373,7 +2490,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2'); - params!.should.deep.equal([_.map(products, 'id'), store.id]); + assert(params); + params.should.deep.equal([_.map(products, 'id'), store.id]); }); it('should support call with chained where constraints', async () => { const products = [ @@ -2401,7 +2519,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products" WHERE "store_id"=$1'); - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with chained where constraints - Promise.all', async () => { const products = [ @@ -2431,7 +2550,8 @@ describe('ReadonlyRepository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products" WHERE "store_id"=$1'); - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); }); }); diff --git a/tests/repository.tests.ts b/tests/repository.tests.ts index ae94e9a..b592163 100644 --- a/tests/repository.tests.ts +++ b/tests/repository.tests.ts @@ -1,3 +1,5 @@ +import assert from 'assert'; + import chai from 'chai'; import * as faker from 'faker'; import * as _ from 'lodash'; @@ -9,6 +11,7 @@ import type { CreateUpdateParams, QueryResult, Repository } from '../src'; import { initialize } from '../src'; import { Product, ProductWithCreateUpdateDateTracking, SimpleWithStringCollection, Store } from './models'; +import * as generator from './utils/generator'; function getQueryResult(rows: T[] = []): PostgresQueryResult { return { @@ -51,6 +54,11 @@ describe('Repository', () => { }); describe('#create()', () => { + let store: QueryResult; + beforeEach(() => { + store = generator.store(); + }); + it('should execute beforeCreate if defined as a schema method', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ @@ -67,15 +75,13 @@ describe('Repository', () => { verify(mockedPool.query(anyString(), anything())).once(); const [, params] = capture(mockedPool.query).first(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal(['beforeCreate - foo', []]); + assert(params); + params.should.deep.equal(['beforeCreate - foo', []]); }); it('should return single object result if single value is specified', async () => { - const product = { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -90,15 +96,13 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3) 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.name, [], product.store]); + assert(params); + params.should.deep.equal([product.name, [], product.store]); }); it('should return single object result if single value is specified - Promise.all', async () => { - const product = { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -115,15 +119,13 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3) 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.name, [], product.store]); + assert(params); + params.should.deep.equal([product.name, [], product.store]); }); it('should return void if single value is specified and returnRecords=false', async () => { - const product = { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -142,8 +144,8 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3)'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal([product.name, [], product.store]); + assert(params); + params.should.deep.equal([product.name, [], product.store]); }); it('should return empty array results if empty value array is specified', async () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([])); @@ -156,25 +158,23 @@ describe('Repository', () => { }); it('should return object array results if multiple values are specified', async () => { const products = [ - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }, - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.create( - products.map((product) => ({ - name: product.name, - store: product.store, - })), + products.map((product) => { + return { + name: product.name, + store: product.store, + }; + }), ); verify(mockedPool.query(anyString(), anything())).once(); @@ -182,30 +182,28 @@ describe('Repository', () => { 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) 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([products[0].name, products[1].name, [], [], products[0].store, products[1].store]); + assert(params); + params.should.deep.equal([products[0].name, products[1].name, [], [], products[0].store, products[1].store]); }); it('should return void if multiple values are specified and returnRecords=false', async () => { const products = [ - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }, - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); const result = await ProductRepository.create( - products.map((product) => ({ - name: product.name, - store: product.store, - })), + products.map((product) => { + return { + name: product.name, + store: product.store, + }; + }), { returnRecords: false, }, @@ -216,19 +214,13 @@ describe('Repository', () => { 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)'); - // 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]); + assert(params); + params.should.deep.equal([products[0].name, products[1].name, [], [], products[0].store, products[1].store]); }); it('should allow populated value parameters', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = faker.datatype.uuid(); - - const product = { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, + const product = generator.product({ store: store.id, - }; + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -243,21 +235,17 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3) 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.name, [], store.id]); + assert(params); + params.should.deep.equal([product.name, [], store.id]); }); it('should allow populated (QueryResult) value parameters', async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = faker.datatype.uuid(); - - const product = { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, + const product = generator.product({ store: store.id, - }; + }); - const storeAsQueryResult: QueryResult = store; + const storeAsQueryResult: QueryResult = { + ...store, + }; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -272,21 +260,17 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3) 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.name, [], store.id]); + assert(params); + params.should.deep.equal([product.name, [], store.id]); }); it(`should allow populated (Pick) value parameters`, async () => { - const store = new Store(); - store.id = faker.datatype.number(); - store.name = faker.datatype.uuid(); - - const product = { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, + const product = generator.product({ store: store.id, - }; + }); - const storeAsPickId = _.pick(store, 'id'); + const storeAsPickId: Pick = { + id: store.id, + }; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -301,15 +285,11 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3) 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.name, [], store.id]); + assert(params); + params.should.deep.equal([product.name, [], store.id]); }); it('should insert with string array value parameter', async () => { - const item = { - id: faker.datatype.number(), - name: `simpleWithStringArray - ${faker.datatype.uuid()}`, - otherIds: [faker.datatype.uuid(), faker.datatype.uuid()], - }; + const item = generator.simpleWithStringCollection(); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([item])); @@ -326,20 +306,14 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "simple" ("name","other_ids") VALUES ($1,$2) RETURNING "id","name","other_ids" AS "otherIds"'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal([item.name, item.otherIds]); + assert(params); + params.should.deep.equal([item.name, item.otherIds]); }); it('should ignore one-to-many collection values', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store, - categories: [], - }; + const product: Product = generator.product({ + store: store.id, + }); + product.categories = []; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store])); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -355,18 +329,14 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "stores" ("name") VALUES ($1) RETURNING "id","name"'); - params!.should.deep.equal([store.name]); + assert(params); + params.should.deep.equal([store.name]); }); it('should ignore many-to-many collection values', async () => { - const category = { - id: faker.datatype.number(), - name: `category - ${faker.datatype.uuid()}`, - }; - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const category = generator.category(); + const product: Product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -384,11 +354,16 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('INSERT INTO "products" ("name","alias_names","store_id") VALUES ($1,$2,$3) 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.name, [], product.store]); + assert(params); + params.should.deep.equal([product.name, [], product.store]); }); }); describe('#update()', () => { + let store: QueryResult; + beforeEach(() => { + store = generator.store(); + }); + it('should execute beforeUpdate if defined as a schema method', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ @@ -412,15 +387,13 @@ describe('Repository', () => { verify(mockedPool.query(anyString(), anything())).once(); const [, params] = capture(mockedPool.query).first(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal(['beforeUpdate - foo', id]); + assert(params); + params.should.deep.equal(['beforeUpdate - foo', id]); }); it('should return array of updated objects if second parameter is not defined', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const product: Product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -439,15 +412,13 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('UPDATE "products" SET "name"=$1,"store_id"=$2 WHERE "id"=$3 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.name, product.store, product.id]); + assert(params); + params.should.deep.equal([product.name, product.store, product.id]); }); it('should return array of updated objects if second parameter is not defined - Promise.all', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const product: Product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -468,15 +439,13 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('UPDATE "products" SET "name"=$1,"store_id"=$2 WHERE "id"=$3 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.name, product.store, product.id]); + assert(params); + params.should.deep.equal([product.name, product.store, product.id]); }); it('should return void if returnRecords=false', async () => { - const product = { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - store: faker.datatype.number(), - }; + const product: Product = generator.product({ + store: store.id, + }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); @@ -498,21 +467,82 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('UPDATE "products" SET "name"=$1,"store_id"=$2 WHERE "id"=$3'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal([product.name, product.store, product.id]); + assert(params); + params.should.deep.equal([product.name, product.store, product.id]); }); - }); - describe('#destroy()', () => { - it('should delete all records and return void if there are no constraints', async () => { - const products = [ + it('should allow populated (QueryResult) value parameters', async () => { + const product = generator.product({ + store: store.id, + }); + + const storeAsQueryResult: QueryResult = { + ...store, + }; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); + + const results = await ProductRepository.update( { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, + id: product.id, }, { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, + name: product.name, + store: storeAsQueryResult, }, + ); + + verify(mockedPool.query(anyString(), anything())).once(); + results.should.deep.equal([product]); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('UPDATE "products" SET "name"=$1,"store_id"=$2 WHERE "id"=$3 RETURNING "id","name","sku","alias_names" AS "aliases","store_id" AS "store"'); + assert(params); + params.should.deep.equal([product.name, store.id, product.id]); + }); + it(`should allow populated (Pick) value parameters`, async () => { + const product = generator.product({ + store: store.id, + }); + + const storeAsPickId: Pick = { + id: store.id, + }; + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); + + const results = await ProductRepository.update( + { + id: product, + }, + { + name: product.name, + store: storeAsPickId, + }, + ); + + verify(mockedPool.query(anyString(), anything())).once(); + results.should.deep.equal([product]); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('UPDATE "products" SET "name"=$1,"store_id"=$2 WHERE "id"=$3 RETURNING "id","name","sku","alias_names" AS "aliases","store_id" AS "store"'); + assert(params); + params.should.deep.equal([product.name, store.id, product.id]); + }); + }); + describe('#destroy()', () => { + let store: QueryResult; + beforeEach(() => { + store = generator.store(); + }); + + it('should delete all records and return void if there are no constraints', async () => { + const products = [ + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -522,19 +552,17 @@ describe('Repository', () => { 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([]); + assert(params); + params.should.deep.equal([]); }); it('should delete all records if empty constraint and return all data if returnRecords=true', async () => { const products = [ - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -545,19 +573,17 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('DELETE FROM "products" 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([]); + assert(params); + 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.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -568,19 +594,17 @@ describe('Repository', () => { 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([]); + assert(params); + 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.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.uuid(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -591,23 +615,17 @@ describe('Repository', () => { 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([]); + assert(params); + params.should.deep.equal([]); }); it('should support call constraints as a parameter', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -620,23 +638,17 @@ describe('Repository', () => { 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]); + assert(params); + 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.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -653,23 +665,17 @@ describe('Repository', () => { const [query, params] = capture(mockedPool.query).first(); query.should.equal('DELETE FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2 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([_.map(products, 'id'), store.id]); + assert(params); + params.should.deep.equal([_.map(products, 'id'), store.id]); }); it('should support call with chained where constraints', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -680,23 +686,17 @@ describe('Repository', () => { 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]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with chained where constraints if returnRecords=true', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -708,23 +708,17 @@ describe('Repository', () => { 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"'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with chained where constraints - Promise.all', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -737,23 +731,17 @@ describe('Repository', () => { 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]); + assert(params); + params.should.deep.equal([store.id]); }); it('should support call with chained where constraints if returnRecords=true - Promise.all', async () => { - const store = { - id: faker.datatype.number(), - name: `store - ${faker.datatype.uuid()}`, - }; const products = [ - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, - { - id: faker.datatype.number(), - name: `product - ${faker.datatype.uuid()}`, - }, + generator.product({ + store: store.id, + }), + generator.product({ + store: store.id, + }), ]; when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult(products)); @@ -767,8 +755,8 @@ describe('Repository', () => { 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"'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - params!.should.deep.equal([store.id]); + assert(params); + params.should.deep.equal([store.id]); }); }); }); diff --git a/tests/sqlHelper.tests.ts b/tests/sqlHelper.tests.ts index 3377866..92ad813 100644 --- a/tests/sqlHelper.tests.ts +++ b/tests/sqlHelper.tests.ts @@ -1,3 +1,5 @@ +import assert from 'assert'; + import chai from 'chai'; import * as faker from 'faker'; import { Pool } from 'postgres-pool'; @@ -895,8 +897,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "store_id"=$1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "store_id"=$1'); params.should.deep.equal([storeId]); }); it('should use property name if columnName is not defined', () => { @@ -909,8 +911,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name"=$1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name"=$1'); params.should.deep.equal([name]); }); it('should handle startsWith', () => { @@ -925,8 +927,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" ILIKE $1'); params.should.deep.equal([`${name}%`]); }); it('should handle startsWith with an array of values', () => { @@ -942,8 +944,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); params.should.deep.equal([[`${name1.toLowerCase()}%`, `${name2.toLowerCase()}%`]]); }); it('should handle endsWith', () => { @@ -958,8 +960,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" ILIKE $1'); params.should.deep.equal([`%${name}`]); }); it('should handle endsWith with an array of values', () => { @@ -975,8 +977,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); params.should.deep.equal([[`%${name1.toLowerCase()}`, `%${name2.toLowerCase()}`]]); }); it('should handle contains', () => { @@ -991,8 +993,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" ILIKE $1'); params.should.deep.equal([`%${name}%`]); }); it('should handle contains with an array of values', () => { @@ -1008,8 +1010,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); params.should.deep.equal([[`%${name1.toLowerCase()}%`, `%${name2.toLowerCase()}%`]]); }); it('should handle like', () => { @@ -1024,8 +1026,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" ILIKE $1'); params.should.deep.equal([name]); }); it('should handle not like', () => { @@ -1042,8 +1044,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" NOT ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" NOT ILIKE $1'); params.should.deep.equal([name]); }); it('should handle like with an empty value', () => { @@ -1057,8 +1059,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" = \'\''); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" = \'\''); params.should.deep.equal([]); }); it('should handle not like with an empty value', () => { @@ -1074,8 +1076,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" != \'\''); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" != \'\''); params.should.deep.equal([]); }); it('should handle like with array with a single value', () => { @@ -1090,8 +1092,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" ILIKE $1'); params.should.deep.equal([name]); }); it('should handle not like with array with a single value', () => { @@ -1108,8 +1110,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" NOT ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" NOT ILIKE $1'); params.should.deep.equal([name]); }); it('should handle like with an array of values', () => { @@ -1125,8 +1127,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE lower("name")=ANY($1::TEXT[])'); params.should.deep.equal([[name1.toLowerCase(), name2.toLowerCase()]]); }); it('should handle like with an array of null, empty string, and single value', () => { @@ -1141,8 +1143,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE ("name" IS NULL OR "name" = \'\' OR "name" ILIKE $1)'); + assert(whereStatement); + whereStatement.should.equal('WHERE ("name" IS NULL OR "name" = \'\' OR "name" ILIKE $1)'); params.should.deep.equal([name]); }); it('should handle not like with an array of values', () => { @@ -1160,8 +1162,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE lower("name")<>ALL($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE lower("name")<>ALL($1::TEXT[])'); params.should.deep.equal([[name1.toLowerCase(), name2.toLowerCase()]]); }); it('should handle not like with an array of null, empty string, and single value', () => { @@ -1178,8 +1180,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" IS NOT NULL AND "name" != \'\' AND "name" NOT ILIKE $1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" IS NOT NULL AND "name" != \'\' AND "name" NOT ILIKE $1'); params.should.deep.equal([name]); }); it('should handle like with an empty array', () => { @@ -1193,8 +1195,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE 1<>1'); + assert(whereStatement); + whereStatement.should.equal('WHERE 1<>1'); params.should.deep.equal([]); }); it('should handle not like with an empty array', () => { @@ -1210,8 +1212,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE 1=1'); + assert(whereStatement); + whereStatement.should.equal('WHERE 1=1'); params.should.deep.equal([]); }); it('should handle like with array column and array with a single value', () => { @@ -1226,8 +1228,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); + assert(whereStatement); + whereStatement.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); params.should.deep.equal([name]); }); it('should handle not like with array column and array with a single value', () => { @@ -1244,8 +1246,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE NOT EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); + assert(whereStatement); + whereStatement.should.equal('WHERE NOT EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); params.should.deep.equal([name]); }); it('should handle like with array column and single value', () => { @@ -1260,8 +1262,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); + assert(whereStatement); + whereStatement.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); params.should.deep.equal([name]); }); it('should handle not like with array column and a single value', () => { @@ -1278,8 +1280,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE NOT EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); + assert(whereStatement); + whereStatement.should.equal('WHERE NOT EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE "unnested_alias_names" ILIKE $1)'); params.should.deep.equal([name]); }); it('should handle like with array column and an array of values', () => { @@ -1295,8 +1297,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE lower("unnested_alias_names")=ANY($1::TEXT[]))'); + assert(whereStatement); + whereStatement.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE lower("unnested_alias_names")=ANY($1::TEXT[]))'); params.should.deep.equal([[name1.toLowerCase(), name2.toLowerCase()]]); }); it('should handle not like with array column and an array of values', () => { @@ -1314,8 +1316,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE lower("unnested_alias_names")<>ALL($1::TEXT[]))'); + assert(whereStatement); + whereStatement.should.equal('WHERE EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE lower("unnested_alias_names")<>ALL($1::TEXT[]))'); params.should.deep.equal([[name1.toLowerCase(), name2.toLowerCase()]]); }); it('should handle date value', () => { @@ -1330,8 +1332,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "created_at">$1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "created_at">$1'); params.should.deep.equal([now]); }); it('should handle or', () => { @@ -1355,8 +1357,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE (("name"=$1) OR ("name"<>$2 AND "store_id"=$3))'); + assert(whereStatement); + whereStatement.should.equal('WHERE (("name"=$1) OR ("name"<>$2 AND "store_id"=$3))'); params.should.deep.equal([name, name, store]); }); it('should handle mixed or/and constraints', () => { @@ -1384,8 +1386,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "id"=$1 AND (("name"=$2) OR ("name"<>$3 AND "store_id"=$4)) AND "sku"=$5'); + assert(whereStatement); + whereStatement.should.equal('WHERE "id"=$1 AND (("name"=$2) OR ("name"<>$3 AND "store_id"=$4)) AND "sku"=$5'); params.should.deep.equal([id, name, name, store, sku]); }); it('should treat string type with array values as an =ANY() statement', () => { @@ -1398,8 +1400,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name"=ANY($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name"=ANY($1::TEXT[])'); params.should.deep.equal([name]); }); it('should treat integer type with array values as an =ANY() statement', () => { @@ -1413,8 +1415,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "int_column"=ANY($1::INTEGER[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE "int_column"=ANY($1::INTEGER[])'); params.should.deep.equal([values]); }); it('should treat float type with array values as an =ANY() statement', () => { @@ -1428,8 +1430,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "float_column"=ANY($1::NUMERIC[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE "float_column"=ANY($1::NUMERIC[])'); params.should.deep.equal([values]); }); describe('type: "array"', () => { @@ -1442,8 +1444,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "array_column"=\'{}\''); + assert(whereStatement); + whereStatement.should.equal('WHERE "array_column"=\'{}\''); params.should.deep.equal([]); }); it('should handle comparing array type as an array of null or empty', () => { @@ -1455,8 +1457,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE ("array_column" IS NULL OR "array_column"=\'{}\')'); + assert(whereStatement); + whereStatement.should.equal('WHERE ("array_column" IS NULL OR "array_column"=\'{}\')'); params.should.deep.equal([]); }); it('should handle comparing array type with single value as =ANY()', () => { @@ -1469,8 +1471,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1=ANY("array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1=ANY("array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with array of a single value as =ANY()', () => { @@ -1483,8 +1485,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1=ANY("array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1=ANY("array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with negated single value as <>ALL()', () => { @@ -1499,8 +1501,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1<>ALL("array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1<>ALL("array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with negated array of a single value as <>ALL()', () => { @@ -1515,8 +1517,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1<>ALL("array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1<>ALL("array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with array value as separate =ANY() statements', () => { @@ -1529,8 +1531,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE ($1=ANY("array_column") OR $2=ANY("array_column"))'); + assert(whereStatement); + whereStatement.should.equal('WHERE ($1=ANY("array_column") OR $2=ANY("array_column"))'); params.should.deep.equal([values[0], values[1]]); }); it('should handle comparing array type with negated array value as separate <>ALL() statements', () => { @@ -1545,8 +1547,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1<>ALL("array_column") AND $2<>ALL("array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1<>ALL("array_column") AND $2<>ALL("array_column")'); params.should.deep.equal([values[0], values[1]]); }); }); @@ -1560,8 +1562,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "string_array_column"=\'{}\''); + assert(whereStatement); + whereStatement.should.equal('WHERE "string_array_column"=\'{}\''); params.should.deep.equal([]); }); it('should handle comparing array type as an array of null or empty', () => { @@ -1574,8 +1576,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE ("string_array_column" IS NULL OR "string_array_column"=\'{}\')'); + assert(whereStatement); + whereStatement.should.equal('WHERE ("string_array_column" IS NULL OR "string_array_column"=\'{}\')'); params.should.deep.equal([]); }); it('should handle comparing array type with single value as =ANY()', () => { @@ -1588,8 +1590,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1=ANY("string_array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1=ANY("string_array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with array of a single value as =ANY()', () => { @@ -1602,8 +1604,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1=ANY("string_array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1=ANY("string_array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with negated single value as <>ALL()', () => { @@ -1618,8 +1620,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1<>ALL("string_array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1<>ALL("string_array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with negated array of a single value as <>ALL()', () => { @@ -1634,8 +1636,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1<>ALL("string_array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1<>ALL("string_array_column")'); params.should.deep.equal([value]); }); it('should handle comparing array type with array value as separate =ANY() statements', () => { @@ -1648,8 +1650,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE ($1=ANY("string_array_column") OR $2=ANY("string_array_column"))'); + assert(whereStatement); + whereStatement.should.equal('WHERE ($1=ANY("string_array_column") OR $2=ANY("string_array_column"))'); params.should.deep.equal([values[0], values[1]]); }); it('should handle comparing array type with negated array value as separate <>ALL() statements', () => { @@ -1664,8 +1666,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE $1<>ALL("string_array_column") AND $2<>ALL("string_array_column")'); + assert(whereStatement); + whereStatement.should.equal('WHERE $1<>ALL("string_array_column") AND $2<>ALL("string_array_column")'); params.should.deep.equal([values[0], values[1]]); }); }); @@ -1678,8 +1680,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE 1<>1'); + assert(whereStatement); + whereStatement.should.equal('WHERE 1<>1'); params.should.deep.equal([]); }); it('should treat negated empty array value as "true"', () => { @@ -1693,8 +1695,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE 1=1'); + assert(whereStatement); + whereStatement.should.equal('WHERE 1=1'); params.should.deep.equal([]); }); it('should handle single value array', () => { @@ -1707,8 +1709,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name"=$1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name"=$1'); params.should.deep.equal([name]); }); it('should handle an array value with NULL explicitly', () => { @@ -1720,8 +1722,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE ("name" IS NULL OR "name"=$1)'); + assert(whereStatement); + whereStatement.should.equal('WHERE ("name" IS NULL OR "name"=$1)'); params.should.deep.equal(['']); }); it('should treat negation of array value as an <>ALL() statement', () => { @@ -1736,8 +1738,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name"<>ALL($1::TEXT[])'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name"<>ALL($1::TEXT[])'); params.should.deep.equal([name]); }); it('should treat negation of empty array value as "true"', () => { @@ -1751,8 +1753,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE 1=1'); + assert(whereStatement); + whereStatement.should.equal('WHERE 1=1'); params.should.deep.equal([]); }); it('should treat negation of array value with NULL explicitly as AND statements', () => { @@ -1766,8 +1768,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "name" IS NOT NULL AND "name"<>$1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "name" IS NOT NULL AND "name"<>$1'); params.should.deep.equal(['']); }); it('should use primaryKey if hydrated object is passed as a query value', () => { @@ -1783,8 +1785,8 @@ describe('sqlHelper', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - whereStatement!.should.equal('WHERE "store_id"=$1'); + assert(whereStatement); + whereStatement.should.equal('WHERE "store_id"=$1'); params.should.deep.equal([store.id]); }); }); From f05213e030a95275851051645c2072578e5d2915 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 17:29:03 -0600 Subject: [PATCH 13/20] Remove optional collections from CreateUpdateParams --- src/types/CreateUpdateParams.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/types/CreateUpdateParams.ts b/src/types/CreateUpdateParams.ts index 88161a8..e9498ff 100644 --- a/src/types/CreateUpdateParams.ts +++ b/src/types/CreateUpdateParams.ts @@ -3,15 +3,14 @@ import type { Entity, NotEntityBrand } from '../Entity'; import type { EntityPrimitiveOrId } from './EntityPrimitiveOrId'; import type { ExcludeEntityCollections } from './ExcludeEntityCollections'; import type { ExcludeFunctions } from './ExcludeFunctions'; -import type { QueryResult } from './QueryResult'; /** * Changes all Entity value properties to Primitive (string|number) | Pick */ export type CreateUpdateParams = { - [K in keyof T as ExcludeEntityCollections>]?: T[K] extends NotEntityBrand | undefined + [K in keyof T as ExcludeEntityCollections, ExcludeFunctions>]?: T[K] extends NotEntityBrand | undefined ? T[K] : T[K] extends Entity - ? EntityPrimitiveOrId | Pick, 'id'> + ? EntityPrimitiveOrId | { id: unknown } : T[K]; }; From f18a05a3325f98a64a56593999781987a75cba2d Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Tue, 4 Jan 2022 19:30:32 -0600 Subject: [PATCH 14/20] Fix CreateUpdateParams and WhereQuery to allow partial objects in some cases --- src/query/WhereQuery.ts | 28 +++++++++----- src/types/CreateUpdateParams.ts | 5 +-- src/types/EntityPrimitiveOrId.ts | 2 +- tests/readonlyRepository.tests.ts | 2 + tests/repository.tests.ts | 62 ++++++++++++++++++++++++++++--- tests/utils/generator.ts | 4 +- 6 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/query/WhereQuery.ts b/src/query/WhereQuery.ts index 211404f..991d577 100644 --- a/src/query/WhereQuery.ts +++ b/src/query/WhereQuery.ts @@ -1,18 +1,27 @@ import type { Entity, NotEntityBrand } from '../Entity'; -import type { EntityPrimitiveOrId, ExcludeEntityCollections, ExcludeFunctions } from '../types'; +import type { ExcludeEntityCollections, ExcludeFunctions } from '../types'; + +type ExcludeUndefined = Exclude; + +export type LiteralValues = (ExcludeUndefined | null)[] | ExcludeUndefined | null; export type WhereClauseValue = TValue extends NotEntityBrand | undefined - ? Exclude - : TValue extends Entity - ? { id: unknown }[] | Exclude, undefined> | Exclude, undefined>[] | { id: unknown } | null - : Exclude<(TValue | null)[] | TValue, undefined> | null; + ? Exclude // If the value is not an entity, return the type without undefined + : Extract, Entity> extends Entity // Otherwise if the type extends Entity + ? + | (ExcludeUndefined> | null)[] // Allow an array of the literal value (non-entity) + | (Pick, Entity>, 'id'> | null)[] // Allow an array of objects with the id property + | ExcludeUndefined> // Allow a single literal value + | Pick, Entity>, 'id'> // Allow a single object with the id property + | null + : LiteralValues; // Otherwise allow literal values export type StringConstraint = { - [P in 'contains' | 'endsWith' | 'like' | 'startsWith']?: WhereClauseValue; + [P in 'contains' | 'endsWith' | 'like' | 'startsWith']?: LiteralValues; }; export type NumberOrDateConstraint = { - [P in '<' | '<=' | '>' | '>=']?: WhereClauseValue; + [P in '<' | '<=' | '>' | '>=']?: LiteralValues; }; export type NegatableConstraint = @@ -34,9 +43,10 @@ export type WhereQuery = { : T[K] extends (infer U)[] | undefined // If property type is an array, allow where query statements for the array type ? WhereQueryStatement : - | Exclude<(T[K] | null)[] | T[K], undefined> // Allow arrays or single object of type + | (ExcludeUndefined | null)[] // Allow array of type + | T[K] // Allow Single object of type | WhereQueryStatement // Allow nested where query statements - | { '!': Exclude<(T[K] | null)[] | T[K], undefined> }; // Allow arrays of union types + | { '!': LiteralValues }; // Allow arrays of union types } & { or?: WhereQuery[]; }; diff --git a/src/types/CreateUpdateParams.ts b/src/types/CreateUpdateParams.ts index e9498ff..9e6b4c2 100644 --- a/src/types/CreateUpdateParams.ts +++ b/src/types/CreateUpdateParams.ts @@ -1,6 +1,5 @@ import type { Entity, NotEntityBrand } from '../Entity'; -import type { EntityPrimitiveOrId } from './EntityPrimitiveOrId'; import type { ExcludeEntityCollections } from './ExcludeEntityCollections'; import type { ExcludeFunctions } from './ExcludeFunctions'; @@ -10,7 +9,7 @@ import type { ExcludeFunctions } from './ExcludeFunctions'; export type CreateUpdateParams = { [K in keyof T as ExcludeEntityCollections, ExcludeFunctions>]?: T[K] extends NotEntityBrand | undefined ? T[K] - : T[K] extends Entity - ? EntityPrimitiveOrId | { id: unknown } + : Extract extends Entity + ? Exclude | Pick, 'id'> : T[K]; }; diff --git a/src/types/EntityPrimitiveOrId.ts b/src/types/EntityPrimitiveOrId.ts index 5ec6610..31c3c88 100644 --- a/src/types/EntityPrimitiveOrId.ts +++ b/src/types/EntityPrimitiveOrId.ts @@ -1,3 +1,3 @@ import type { Entity } from '../Entity'; -export type EntityPrimitiveOrId = T extends Entity ? Exclude | Pick : T; +export type EntityPrimitiveOrId = Extract, Entity> extends Entity ? Exclude, Entity> | Pick : T; diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index 83cce4c..b6074a5 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -1587,6 +1587,8 @@ describe('ReadonlyRepository', () => { }); when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([one]), getQueryResult([two]), getQueryResult([three1, three2])); + assert(one.foo); + assert(two.foo); assert(three1.foo); assert(three2.foo); diff --git a/tests/repository.tests.ts b/tests/repository.tests.ts index b592163..6357d8d 100644 --- a/tests/repository.tests.ts +++ b/tests/repository.tests.ts @@ -10,7 +10,7 @@ import { anyString, anything, capture, instance, mock, reset, verify, when } fro import type { CreateUpdateParams, QueryResult, Repository } from '../src'; import { initialize } from '../src'; -import { Product, ProductWithCreateUpdateDateTracking, SimpleWithStringCollection, Store } from './models'; +import { Category, Product, ProductCategory, ProductWithCreateUpdateDateTracking, SimpleWithStringCollection, Store } from './models'; import * as generator from './utils/generator'; function getQueryResult(rows: T[] = []): PostgresQueryResult { @@ -26,24 +26,24 @@ function getQueryResult(rows: T[] = []): PostgresQueryResult { describe('Repository', () => { let should: Chai.Should; const mockedPool: Pool = mock(Pool); - // eslint-disable-next-line @typescript-eslint/naming-convention + /* eslint-disable @typescript-eslint/naming-convention */ let ProductRepository: Repository; - // eslint-disable-next-line @typescript-eslint/naming-convention + let ProductCategoryRepository: Repository; let SimpleWithStringCollectionRepository: Repository; - // eslint-disable-next-line @typescript-eslint/naming-convention let StoreRepository: Repository; - // eslint-disable-next-line @typescript-eslint/naming-convention let ProductWithCreateUpdateDateTrackingRepository: Repository; + /* eslint-enable @typescript-eslint/naming-convention */ before(() => { should = chai.should(); const repositoriesByModelName = initialize({ - models: [Product, ProductWithCreateUpdateDateTracking, SimpleWithStringCollection, Store], + models: [Category, Product, ProductCategory, ProductWithCreateUpdateDateTracking, SimpleWithStringCollection, Store], pool: instance(mockedPool), }); ProductRepository = repositoriesByModelName.Product as Repository; + ProductCategoryRepository = repositoriesByModelName.ProductCategory as Repository; SimpleWithStringCollectionRepository = repositoriesByModelName.SimpleWithStringCollection as Repository; StoreRepository = repositoriesByModelName.Store as Repository; ProductWithCreateUpdateDateTrackingRepository = repositoriesByModelName.ProductWithCreateUpdateDateTracking as Repository; @@ -263,6 +263,29 @@ describe('Repository', () => { assert(params); params.should.deep.equal([product.name, [], store.id]); }); + it('should allow partial Entity (omitting some required fields) as a value parameter', async () => { + const category = generator.category(); + const product: Pick = generator.product({ + store: store.id, + }); + const productCategory = generator.productCategory(product, category); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([productCategory])); + + const result = await ProductCategoryRepository.create({ + product, + category, + }); + + verify(mockedPool.query(anyString(), anything())).once(); + should.exist(result); + result.should.deep.equal(productCategory); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('INSERT INTO "product__category" ("product_id","category_id") VALUES ($1,$2) RETURNING "id","product_id" AS "product","category_id" AS "category"'); + assert(params); + params.should.deep.equal([product.id, category.id]); + }); it(`should allow populated (Pick) value parameters`, async () => { const product = generator.product({ store: store.id, @@ -528,6 +551,33 @@ describe('Repository', () => { assert(params); params.should.deep.equal([product.name, store.id, product.id]); }); + it('should allow partial Entity (omitting some required fields) as a value parameter', async () => { + const category = generator.category(); + const product: Pick = generator.product({ + store: store.id, + }); + const productCategory = generator.productCategory(product, category); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([productCategory])); + + const results = await ProductCategoryRepository.update( + { + id: productCategory.id, + }, + { + product, + category, + }, + ); + + verify(mockedPool.query(anyString(), anything())).once(); + results.should.deep.equal([productCategory]); + + const [query, params] = capture(mockedPool.query).first(); + query.should.equal('UPDATE "product__category" SET "product_id"=$1,"category_id"=$2 WHERE "id"=$3 RETURNING "id","product_id" AS "product","category_id" AS "category"'); + assert(params); + params.should.deep.equal([product.id, category.id, productCategory.id]); + }); }); describe('#destroy()', () => { let store: QueryResult; diff --git a/tests/utils/generator.ts b/tests/utils/generator.ts index d832a2b..4a3ec41 100644 --- a/tests/utils/generator.ts +++ b/tests/utils/generator.ts @@ -44,7 +44,7 @@ export function category(args?: Partial>): QueryResult | number, categoryInput: QueryResult | number): QueryResult { +export function productCategory(productInput: Pick, 'id'> | number, categoryInput: Pick, 'id'> | number): QueryResult { return { id: faker.datatype.number(), product: (productInput as QueryResult).id || (productInput as number), @@ -70,7 +70,7 @@ export function classroom(args?: Partial>): QueryResult | string, classroomInput: QueryResult | string): QueryResult { +export function teacherClassroom(teacherInput: Pick, 'id'> | string, classroomInput: Pick, 'id'> | string): QueryResult { return { id: `TeacherClassroom - ${faker.datatype.uuid()}`, teacher: (teacherInput as QueryResult).id || (teacherInput as string), From 7e1cedc5864a232e45e09f2523efdaf6f0c40ab7 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Wed, 5 Jan 2022 13:46:37 -0600 Subject: [PATCH 15/20] Check when a property does not extend Entity --- src/query/WhereQuery.ts | 10 +++++----- src/types/CreateUpdateParams.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/query/WhereQuery.ts b/src/query/WhereQuery.ts index 991d577..943344d 100644 --- a/src/query/WhereQuery.ts +++ b/src/query/WhereQuery.ts @@ -6,15 +6,15 @@ type ExcludeUndefined = Exclude; export type LiteralValues = (ExcludeUndefined | null)[] | ExcludeUndefined | null; export type WhereClauseValue = TValue extends NotEntityBrand | undefined - ? Exclude // If the value is not an entity, return the type without undefined - : Extract, Entity> extends Entity // Otherwise if the type extends Entity - ? + ? Exclude // If the value is a NotEntityBrand, return the type without undefined + : Extract extends undefined // Otherwise if the type does not extend Entity + ? LiteralValues + : | (ExcludeUndefined> | null)[] // Allow an array of the literal value (non-entity) | (Pick, Entity>, 'id'> | null)[] // Allow an array of objects with the id property | ExcludeUndefined> // Allow a single literal value | Pick, Entity>, 'id'> // Allow a single object with the id property - | null - : LiteralValues; // Otherwise allow literal values + | null; export type StringConstraint = { [P in 'contains' | 'endsWith' | 'like' | 'startsWith']?: LiteralValues; diff --git a/src/types/CreateUpdateParams.ts b/src/types/CreateUpdateParams.ts index 9e6b4c2..6ff48ac 100644 --- a/src/types/CreateUpdateParams.ts +++ b/src/types/CreateUpdateParams.ts @@ -9,7 +9,7 @@ import type { ExcludeFunctions } from './ExcludeFunctions'; export type CreateUpdateParams = { [K in keyof T as ExcludeEntityCollections, ExcludeFunctions>]?: T[K] extends NotEntityBrand | undefined ? T[K] - : Extract extends Entity - ? Exclude | Pick, 'id'> - : T[K]; + : Extract extends undefined + ? T[K] + : Exclude | Pick, 'id'>; }; From e283dfa93c640be518fa38eb44a5428c648d72ed Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Wed, 5 Jan 2022 13:50:02 -0600 Subject: [PATCH 16/20] Remove Node v12 and add Node v17 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41adb8b..f051baa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [14.x, 16.x, 17.x] steps: - uses: actions/checkout@v2 From 2b4284f96593ef49ac16e3585f3aa73bc5a51cb8 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Wed, 5 Jan 2022 13:52:42 -0600 Subject: [PATCH 17/20] Remove optional property operator --- tests/readonlyRepository.tests.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index b6074a5..7408b83 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -1128,7 +1128,8 @@ describe('ReadonlyRepository', () => { const result = await SimpleWithJsonRepository.findOne(); assert(result); result.should.deep.equal(simple); - result.keyValue?.should.deep.equal(simple.keyValue); + assert(result.keyValue); + result.keyValue.should.deep.equal(simple.keyValue); const [query, params] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","bar","key_value" AS "keyValue" FROM "simple" LIMIT 1'); @@ -1207,7 +1208,8 @@ describe('ReadonlyRepository', () => { productResult.store = storeResult; productResult.store.id.should.equal(store.id); - productResult.store.name?.should.equal(store.name); + assert(productResult.store.name); + productResult.store.name.should.equal(store.name); }); it('should support manually setting a field - UNSAFE_withFieldValue()', async () => { when(mockedPool.query(anyString(), anything())).thenResolve( @@ -1225,7 +1227,8 @@ describe('ReadonlyRepository', () => { assert(productResult); productResult.store.id.should.equal(store.id); - productResult.store.name?.should.equal(store.name); + assert(productResult.store.name); + productResult.store.name.should.equal(store.name); }); }); describe('#find()', () => { @@ -1653,7 +1656,8 @@ describe('ReadonlyRepository', () => { productResult.store = storeResult; productResult.store.id.should.equal(store.id); - productResult.store.name?.should.equal(store.name); + assert(productResult.store.name); + productResult.store.name.should.equal(store.name); }); describe('populate', () => { let store1: QueryResult; From 015a49dfe4ca9b36af64a58e363e05baecf36f11 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Wed, 5 Jan 2022 14:41:03 -0600 Subject: [PATCH 18/20] Remove `return await` in favor of `return` --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b20c650..9a5de83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "10.0.0-beta1", + "version": "10.0.0-beta5", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", @@ -58,6 +58,7 @@ "typescript": "^4.5.4" }, "scripts": { + "check:types": "tsc -p tsconfig.lint.json --noEmit", "prebuild": "rimraf dist", "build": "tsc", "test": "mocha -r ts-node/register tests/**/*.tests.ts", @@ -65,7 +66,7 @@ "lint:code": "eslint --fix --ext .ts src tests", "lint": "run-p lint:*", "lint-staged": "lint-staged", - "dist": "if [ -d \"src\" ]; then rm -rf dist && npm run build && npm run lint && npm run test && cp package.json dist && cp package-lock.json dist && cp *.md dist && cp LICENSE dist && cp .npmignore dist && cd dist && npm publish; fi", + "dist": "if [ -d \"src\" ]; then rm -rf dist && npm run build && npm run lint && npm run test && cp package.json dist && cp package-lock.json dist && cp *.md dist && cp LICENSE dist && cp .npmignore dist && cd dist && npm publish --tag beta; fi", "prepublishOnly": "if [ -d \"src\" ]; then echo \"Please use: npm run dist\" && exit 125; fi && pinst --disable", "postinstall": "husky install", "postpublish": "pinst --enable" From 428cbfdf96c72cfcb77779d95bebeed81b9dda12 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Fri, 7 Jan 2022 23:36:35 -0600 Subject: [PATCH 19/20] Add QueryResultOptionalPopulated helper type --- src/types/EntityPrimitiveOrId.ts | 8 +++++++- src/types/QueryResultOptionalPopulated.ts | 15 +++++++++++++++ src/types/index.ts | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/types/QueryResultOptionalPopulated.ts diff --git a/src/types/EntityPrimitiveOrId.ts b/src/types/EntityPrimitiveOrId.ts index 31c3c88..4524595 100644 --- a/src/types/EntityPrimitiveOrId.ts +++ b/src/types/EntityPrimitiveOrId.ts @@ -1,3 +1,9 @@ import type { Entity } from '../Entity'; -export type EntityPrimitiveOrId = Extract, Entity> extends Entity ? Exclude, Entity> | Pick : T; +export type EntityPrimitiveOrId = T extends [] + ? T extends (infer U)[] + ? EntityPrimitiveOrId[] + : T // Unable to determine array type, so return original + : Extract, Entity> extends undefined + ? T + : Exclude, Entity> | Pick, Entity>, 'id'>; diff --git a/src/types/QueryResultOptionalPopulated.ts b/src/types/QueryResultOptionalPopulated.ts new file mode 100644 index 0000000..fc71902 --- /dev/null +++ b/src/types/QueryResultOptionalPopulated.ts @@ -0,0 +1,15 @@ +import type { Entity } from '../Entity'; + +import type { EntityPrimitiveOrId } from './EntityPrimitiveOrId'; +import type { QueryResult } from './QueryResult'; + +/** + * Allows a QueryResult type with specific properties optionally populated. If the property is populated, only the id property is needed + */ +export type QueryResultOptionalPopulated = Omit, K> & { + [P in K]-?: T[P] extends [] + ? undefined extends T[P] // If property is not an array + ? EntityPrimitiveOrId | null // If the property is originally optional, include null as a possible value type + : EntityPrimitiveOrId // Otherwise, use the TPropertyType + : EntityPrimitiveOrId; // Otherwise return an array of items +}; diff --git a/src/types/index.ts b/src/types/index.ts index 574c80c..e7f5723 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -13,3 +13,4 @@ export * from './PickFunctions'; export * from './Populated'; export * from './QueryResult'; export * from './QueryResultPopulated'; +export * from './QueryResultOptionalPopulated'; From b9d8e75f137ed58cb55c84a410344424146b0e3c Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Fri, 7 Jan 2022 23:37:14 -0600 Subject: [PATCH 20/20] Release v10.0.0 - Fix populated entities to be QueryResult - Improve return types when doing partial selects - Be less restrictive with dependency versions - Drop node 12 support --- CHANGELOG.md | 2 +- package-lock.json | 8 ++++---- package.json | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8848faa..da830df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 10.0.0-beta1 - 2022-01-03 +## 10.0.0 - 2022-01-07 - Fix populated entities to be QueryResult - Improve return types when doing partial selects diff --git a/package-lock.json b/package-lock.json index 23e186f..880b19e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "10.0.0-beta1", + "version": "10.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1957,9 +1957,9 @@ } }, "lint-staged": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.5.tgz", - "integrity": "sha512-WyKb+0sNKDTd1LwwAfTBPp0XmdaKkAOEbg4oHE4Kq2+oQVchg/VAcjVQtSqZih1izNsTURjc2EkhG/syRQUXdA==", + "version": "12.1.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.7.tgz", + "integrity": "sha512-bltv/ejiLWtowExpjU+s5z8j1Byjg9AlmaAjMmqNbIicY69u6sYIwXGg0dCn0TlkrrY2CphtHIXAkbZ+1VoWQQ==", "dev": true, "requires": { "cli-truncate": "^3.1.0", diff --git a/package.json b/package.json index 9a5de83..5848abc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "10.0.0-beta5", + "version": "10.0.0", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", @@ -45,7 +45,7 @@ "eslint-plugin-security": "^1.4.0", "faker": "^5.5.3", "husky": "^7.0.4", - "lint-staged": "^12.1.5", + "lint-staged": "^12.1.7", "markdownlint-cli": "^0.30.0", "mocha": "^9.1.3", "npm-run-all": "^4.1.5", @@ -66,7 +66,7 @@ "lint:code": "eslint --fix --ext .ts src tests", "lint": "run-p lint:*", "lint-staged": "lint-staged", - "dist": "if [ -d \"src\" ]; then rm -rf dist && npm run build && npm run lint && npm run test && cp package.json dist && cp package-lock.json dist && cp *.md dist && cp LICENSE dist && cp .npmignore dist && cd dist && npm publish --tag beta; fi", + "dist": "if [ -d \"src\" ]; then rm -rf dist && npm run build && npm run lint && npm run test && cp package.json dist && cp package-lock.json dist && cp *.md dist && cp LICENSE dist && cp .npmignore dist && cd dist && npm publish; fi", "prepublishOnly": "if [ -d \"src\" ]; then echo \"Please use: npm run dist\" && exit 125; fi && pinst --disable", "postinstall": "husky install", "postpublish": "pinst --enable"