diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf5259..e3e4ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### 7.1.0 + * Fix `select` typings for populate() calls + * Changed `Entity` to be an abstract class rather than an interface + +NOTE: This is a pretty big breaking change, but v7.0.0 was less than 24h old and was broken, so leaving this as a +minor version change. + ### 7.0.0 * Add generic types to select and where. #72 Thanks @krislefeber! * Add debug environment variable to print sql to console. #73 Thanks @krislefeber! diff --git a/README.md b/README.md index 9f00a28..dfed2a9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ $ npm install pg postgres-pool bigal #### Defining database models +Model definitions need to extend `Entity`. + ```typescript import { Entity } from 'bigal'; import { column, primaryColumn, table } from 'bigal/decorators'; @@ -37,7 +39,7 @@ import { ProductCategory } from './ProductCategory'; @table({ name: 'products', }) -export class Product implements Entity { +export class Product extends Entity { @primaryColumn({ type: 'integer' }) public id!: number; diff --git a/package.json b/package.json index ac411b2..3a275fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "7.0.0", + "version": "7.1.0", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", diff --git a/src/Entity.ts b/src/Entity.ts index 6b24c40..2e9393d 100644 --- a/src/Entity.ts +++ b/src/Entity.ts @@ -1,8 +1,11 @@ export type EntityFieldValue = boolean[] | Date | number[] | Record | string[] | boolean | number | string | unknown | null; +// NOTE: Using declaration merging so that IsValueOfType can identify classes that extend Entity, while +// not having __bigAlEntity carry over/transpile to subclassed objects +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export abstract class Entity {} export interface Entity { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [index: string]: any; + __bigAlEntity: true; } export interface EntityStatic { diff --git a/src/IReadonlyRepository.ts b/src/IReadonlyRepository.ts index d915c1c..f8969a5 100644 --- a/src/IReadonlyRepository.ts +++ b/src/IReadonlyRepository.ts @@ -1,7 +1,8 @@ +import type { Entity } from './Entity'; import type { ModelMetadata } from './metadata'; import type { CountResult, FindArgs, FindOneArgs, FindOneResult, FindResult, WhereQuery } from './query'; -export interface IReadonlyRepository { +export interface IReadonlyRepository { readonly model: ModelMetadata; /** diff --git a/src/ReadonlyRepository.ts b/src/ReadonlyRepository.ts index 6e86599..864a34c 100644 --- a/src/ReadonlyRepository.ts +++ b/src/ReadonlyRepository.ts @@ -1,23 +1,23 @@ import _ from 'lodash'; import type { Pool } from 'postgres-pool'; -import type { EntityFieldValue, EntityStatic } from './Entity'; +import type { Entity, EntityFieldValue, EntityStatic } from './Entity'; import type { IReadonlyRepository } from './IReadonlyRepository'; 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 } from './query'; import { getCountQueryAndParams, getSelectQueryAndParams } from './SqlHelper'; -import type { GetPropertyType } from './types/GetPropertyType'; +import type { GetValueType, PickByValueType } from './types'; -export interface IRepositoryOptions { +export interface IRepositoryOptions { modelMetadata: ModelMetadata; type: EntityStatic; - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; pool: Pool; readonlyPool?: Pool; } -export class ReadonlyRepository implements IReadonlyRepository { +export class ReadonlyRepository implements IReadonlyRepository { private readonly _modelMetadata: ModelMetadata; protected _type: EntityStatic; @@ -26,7 +26,7 @@ export class ReadonlyRepository implements IReadonlyRepository { protected _readonlyPool: Pool; - protected _repositoriesByModelNameLowered: Record | IRepository>; + protected _repositoriesByModelNameLowered: Record | IRepository>; protected _floatProperties: string[] = []; @@ -94,9 +94,9 @@ export class ReadonlyRepository implements IReadonlyRepository { interface Populates { propertyName: string; - where?: WhereQuery; + where?: WhereQuery; select?: string[]; - sort?: SortObject | string; + sort?: SortObject | string; skip?: number; limit?: number; } @@ -127,7 +127,7 @@ 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( + populate>( propertyName: TProperty, { where: populateWhere, // @@ -135,7 +135,7 @@ export class ReadonlyRepository implements IReadonlyRepository { sort: populateSort, skip: populateSkip, limit: populateLimit, - }: PopulateArgs> = {}, + }: PopulateArgs[TProperty], Entity>> = {}, ): FindOneResult { populates.push({ propertyName, @@ -207,7 +207,7 @@ export class ReadonlyRepository implements IReadonlyRepository { select: populate.select, where: populateWhere, sort: populate.sort, - } as FindOneArgs); + } as FindOneArgs); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) @@ -238,7 +238,8 @@ export class ReadonlyRepository implements IReadonlyRepository { } if (collectionColumn.through) { - const throughRepository = modelInstance._repositoriesByModelNameLowered[collectionColumn.through.toLowerCase()]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const throughRepository = modelInstance._repositoriesByModelNameLowered[collectionColumn.through.toLowerCase()] as IReadonlyRepository; if (!throughRepository) { throw new Error(`Unable to find repository for multi-map collection: ${collectionColumn.through}. From ${column.target}#${populate.propertyName}`); } @@ -260,7 +261,7 @@ export class ReadonlyRepository implements IReadonlyRepository { (async function populateMultiMulti(): Promise { if (relatedModelColumn) { const mapRecords = await throughRepository.find({ - select: [relatedModelColumn.via] as (string & keyof T)[], + select: [relatedModelColumn.via], where: { [collectionColumn.via]: id, } as WhereQuery, @@ -280,7 +281,7 @@ export class ReadonlyRepository implements IReadonlyRepository { sort: populate.sort, skip: populate.skip, limit: populate.limit, - } as FindArgs); + } as FindArgs); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) @@ -302,7 +303,7 @@ export class ReadonlyRepository implements IReadonlyRepository { sort: populate.sort, skip: populate.skip, limit: populate.limit, - } as FindArgs); + } as FindArgs); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) @@ -602,7 +603,7 @@ export class ReadonlyRepository implements IReadonlyRepository { } else if (_.isObject(sorts)) { for (const [propertyName, order] of Object.entries(sorts)) { let descending = false; - if (order === -1 || order === '-1' || /desc/i.test(`${order as string}`)) { + if (order === -1 || /desc/i.test(`${order}`)) { descending = true; } diff --git a/src/Repository.ts b/src/Repository.ts index 39e72d6..b868c42 100644 --- a/src/Repository.ts +++ b/src/Repository.ts @@ -146,7 +146,7 @@ export class Repository extends ReadonlyRepository implemen } let returnRecords = true; - let returnSelect: string[] | undefined; + let returnSelect: (string & keyof T)[] | undefined; if (options) { if ((options as DoNotReturnRecords).returnRecords === false) { returnRecords = false; diff --git a/src/SqlHelper.ts b/src/SqlHelper.ts index 8143376..2c8f7c1 100644 --- a/src/SqlHelper.ts +++ b/src/SqlHelper.ts @@ -39,9 +39,9 @@ export function getSelectQueryAndParams({ skip, limit, }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; - select?: string[]; + select?: (string & keyof T)[]; where?: WhereQuery; sorts: OrderBy[]; skip: number; @@ -125,7 +125,7 @@ export function getCountQueryAndParams({ model, where, }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; where?: WhereQuery; }): QueryAndParams { @@ -164,11 +164,11 @@ export function getInsertQueryAndParams({ returnRecords = true, returnSelect, }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; - values: Partial | Partial[]; + values: Partial | Partial[]; returnRecords?: boolean; - returnSelect?: Extract[]; + returnSelect?: (string & keyof T)[]; }): QueryAndParams { const entitiesToInsert = _.isArray(values) ? values : [values]; const columnsToInsert = []; @@ -194,11 +194,13 @@ export function getInsertQueryAndParams({ let includePropertyName = false; for (const entity of entitiesToInsert) { // If there is a default value for the property and it is not defined, use the default - if (hasDefaultValue && _.isUndefined(entity[column.propertyName])) { - entity[column.propertyName] = defaultValue; + if (hasDefaultValue && _.isUndefined(entity[column.propertyName as string & keyof T])) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - string is not assignable to T[string & keyof T] | undefined + entity[column.propertyName as string & keyof T] = defaultValue; } - if (_.isUndefined(entity[column.propertyName])) { + if (_.isUndefined(entity[column.propertyName as string & keyof T])) { if (column.required) { throw new Error(`Create statement for "${model.name}" is missing value for required field: ${column.propertyName}`); } @@ -225,7 +227,7 @@ export function getInsertQueryAndParams({ for (const [entityIndex, entity] of entitiesToInsert.entries()) { let value; - const entityValue = entity[column.propertyName] as EntityFieldValue; + const entityValue = entity[column.propertyName as string & keyof T] as EntityFieldValue; if (_.isNil(entityValue)) { value = 'NULL'; } else { @@ -243,7 +245,7 @@ export function getInsertQueryAndParams({ throw new Error(`Unable to find primary key column for ${relatedModelName} when inserting ${model.name}.${column.propertyName} value.`); } - const primaryKeyValue = (entityValue as Partial)[relatedModelPrimaryKey.propertyName] as EntityFieldValue; + const primaryKeyValue = (entityValue as Partial)[relatedModelPrimaryKey.propertyName as string & keyof T] as EntityFieldValue; if (_.isNil(primaryKeyValue)) { throw new Error(`Undefined primary key value for hydrated object value for "${column.propertyName}" on "${model.name}"`); } @@ -309,17 +311,18 @@ export function getUpdateQueryAndParams({ returnRecords = true, returnSelect, }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; where: WhereQuery; - values: Partial; + values: Partial; returnRecords?: boolean; - returnSelect?: Extract[]; + returnSelect?: (string & keyof T)[]; }): QueryAndParams { for (const column of model.updateDateColumns) { - if (_.isUndefined(values[column.propertyName])) { - // eslint-disable-next-line no-param-reassign - values[column.propertyName] = new Date(); + if (_.isUndefined(values[column.propertyName as string & keyof T])) { + // eslint-disable-next-line no-param-reassign, @typescript-eslint/ban-ts-comment + // @ts-ignore - Date is not assignable to T[string & keyof T] + values[column.propertyName as string & keyof T] = new Date(); } } @@ -351,7 +354,7 @@ export function getUpdateQueryAndParams({ throw new Error(`Unable to find primary key column for ${relatedModelName} when inserting ${model.name}.${column.propertyName} value.`); } - const primaryKeyValue = (value as Partial)[relatedModelPrimaryKey.propertyName] as EntityFieldValue; + const primaryKeyValue = (value as Partial)[relatedModelPrimaryKey.propertyName as string & keyof T] as EntityFieldValue; if (_.isNil(primaryKeyValue)) { throw new Error(`Undefined primary key value for hydrated object value for "${column.propertyName}" on "${model.name}"`); } @@ -376,7 +379,7 @@ export function getUpdateQueryAndParams({ } for (const column of model.versionColumns) { - if (!_.isUndefined(values[column.propertyName])) { + if (!_.isUndefined(values[column.propertyName as string & keyof T])) { if (!isFirstProperty) { query += ','; } @@ -429,11 +432,11 @@ export function getDeleteQueryAndParams({ returnRecords = true, returnSelect, }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; where?: WhereQuery; returnRecords?: boolean; - returnSelect?: Extract[]; + returnSelect?: (string & keyof T)[]; }): QueryAndParams { let query = `DELETE FROM "${model.tableName}"`; @@ -469,20 +472,20 @@ export function getDeleteQueryAndParams({ * @returns {string} SQL columns * @private */ -export function getColumnsToSelect({ model, select }: { model: ModelMetadata; select?: Extract[] }): string { +export function getColumnsToSelect({ model, select }: { model: ModelMetadata; select?: (string & keyof T)[] }): string { if (select) { const { primaryKeyColumn } = model; // Include primary key column if it's not defined - if (primaryKeyColumn && !select.includes(primaryKeyColumn.propertyName)) { - select.push(primaryKeyColumn.propertyName); + if (primaryKeyColumn && !select.includes(primaryKeyColumn.propertyName as string & keyof T)) { + select.push(primaryKeyColumn.propertyName as string & keyof T); } } else { // eslint-disable-next-line no-param-reassign select = []; for (const column of model.columns) { if (!(column as ColumnCollectionMetadata).collection) { - select.push(column.propertyName); + select.push(column.propertyName as string & keyof T); } } } @@ -524,7 +527,7 @@ export function buildWhereStatement({ where, params = [], }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; where?: WhereQuery; params?: unknown[]; @@ -611,7 +614,7 @@ function buildWhere({ value, params = [], }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; propertyName?: string; comparer?: Comparer | string; @@ -768,7 +771,7 @@ function buildWhere({ throw new Error(`Unable to find primary key column for ${column.model} specified in where clause for ${model.name}.${column.propertyName}`); } - const primaryKeyValue = (value as Partial)[relatedModelPrimaryKey.propertyName] as EntityFieldValue; + const primaryKeyValue = (value as Partial)[relatedModelPrimaryKey.propertyName as string & keyof T] as EntityFieldValue; if (!_.isNil(primaryKeyValue)) { // Treat `value` as a hydrated object return buildWhere({ @@ -953,7 +956,7 @@ function buildOrOperatorStatement({ value, params = [], }: { - repositoriesByModelNameLowered: Record | IRepository>; + repositoriesByModelNameLowered: Record | IRepository>; model: ModelMetadata; isNegated: boolean; value: number[] | string[]; diff --git a/src/query/CountResult.ts b/src/query/CountResult.ts index 522ed5c..88c0bb3 100644 --- a/src/query/CountResult.ts +++ b/src/query/CountResult.ts @@ -1,7 +1,8 @@ import type { ChainablePromiseLike } from '../ChainablePromiseLike'; +import type { Entity } from '../Entity'; import type { WhereQuery } from './WhereQuery'; -export interface CountResult extends ChainablePromiseLike { +export interface CountResult extends ChainablePromiseLike { where(args: WhereQuery): CountResult | number; } diff --git a/src/query/CreateUpdateOptions.ts b/src/query/CreateUpdateOptions.ts index 7449500..8a99e3e 100644 --- a/src/query/CreateUpdateOptions.ts +++ b/src/query/CreateUpdateOptions.ts @@ -1,4 +1,6 @@ +import type { Entity } from '../Entity'; + import type { DoNotReturnRecords } from './DoNotReturnRecords'; import type { ReturnSelect } from './ReturnSelect'; -export type CreateUpdateOptions = DoNotReturnRecords | ReturnSelect; +export type CreateUpdateOptions = DoNotReturnRecords | ReturnSelect; diff --git a/src/query/DeleteOptions.ts b/src/query/DeleteOptions.ts index 99f4efc..c94ea70 100644 --- a/src/query/DeleteOptions.ts +++ b/src/query/DeleteOptions.ts @@ -1,11 +1,13 @@ -interface ReturnSelect { +import type { Entity } from '../Entity'; + +interface ReturnSelect { returnSelect: (string & keyof T)[]; returnRecords?: true; } -interface ReturnRecords { +interface ReturnRecords { returnRecords: true; returnSelect?: (string & keyof T)[]; } -export type DeleteOptions = ReturnRecords | ReturnSelect; +export type DeleteOptions = ReturnRecords | ReturnSelect; diff --git a/src/query/DestroyResult.ts b/src/query/DestroyResult.ts index a7de81b..7f5f7c4 100644 --- a/src/query/DestroyResult.ts +++ b/src/query/DestroyResult.ts @@ -1,7 +1,8 @@ import type { ChainablePromiseLike } from '../ChainablePromiseLike'; +import type { Entity } from '../Entity'; import type { WhereQuery } from './WhereQuery'; -export interface DestroyResult extends ChainablePromiseLike { +export interface DestroyResult extends ChainablePromiseLike { where(args: WhereQuery): DestroyResult; } diff --git a/src/query/FindArgs.ts b/src/query/FindArgs.ts index 4b8dc8d..23f18a7 100644 --- a/src/query/FindArgs.ts +++ b/src/query/FindArgs.ts @@ -1,6 +1,8 @@ +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 363afde..cb1c468 100644 --- a/src/query/FindOneArgs.ts +++ b/src/query/FindOneArgs.ts @@ -1,7 +1,9 @@ +import type { Entity } from '../Entity'; + import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface FindOneArgs { +export interface FindOneArgs { select?: (string & keyof T)[]; where?: WhereQuery; sort?: Sort; diff --git a/src/query/FindOneResult.ts b/src/query/FindOneResult.ts index 9aa54bc..c2d64be 100644 --- a/src/query/FindOneResult.ts +++ b/src/query/FindOneResult.ts @@ -1,12 +1,13 @@ import type { ChainablePromiseLike } from '../ChainablePromiseLike'; -import type { GetPropertyType } from '../types/GetPropertyType'; +import type { Entity } from '../Entity'; +import type { GetValueType, PickByValueType } from '../types'; import type { PopulateArgs } from './PopulateArgs'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface FindOneResult extends ChainablePromiseLike { - where(args: WhereQuery): FindOneResult; - populate(propertyName: TProperty, options?: PopulateArgs>): FindOneResult; - sort(value: Sort): FindOneResult; +export interface FindOneResult extends ChainablePromiseLike { + where(args: WhereQuery): FindOneResult; + populate>(propertyName: TProperty, options?: PopulateArgs[TProperty], Entity>>): FindOneResult; + sort(value: Sort): FindOneResult; } diff --git a/src/query/FindResult.ts b/src/query/FindResult.ts index 90ff7c2..026b32d 100644 --- a/src/query/FindResult.ts +++ b/src/query/FindResult.ts @@ -1,10 +1,11 @@ import type { ChainablePromiseLike } from '../ChainablePromiseLike'; +import type { Entity } from '../Entity'; import type { PaginateOptions } from './PaginateOptions'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface FindResult extends ChainablePromiseLike { +export interface FindResult extends ChainablePromiseLike { where(args: WhereQuery): FindResult; sort(value: Sort): FindResult; limit(value: number): FindResult; diff --git a/src/query/PopulateArgs.ts b/src/query/PopulateArgs.ts index 5a0c1e6..404430d 100644 --- a/src/query/PopulateArgs.ts +++ b/src/query/PopulateArgs.ts @@ -1,9 +1,10 @@ -import type { GetPropertyType } from '../types/GetPropertyType'; +import type { Entity } from '../Entity'; +import type { GetValueType, PickByValueType } from '../types'; import type { Sort } from './Sort'; import type { WhereQuery } from './WhereQuery'; -export interface PopulateArgs { +export interface PopulateArgs { where?: WhereQuery; select?: (string & keyof T)[]; sort?: Sort; @@ -11,6 +12,6 @@ export interface PopulateArgs { limit?: number; } -export interface PopulatedProperty extends PopulateArgs> { +export interface PopulatedProperty> extends PopulateArgs[TProperty], Entity>> { propertyName: TProperty; } diff --git a/src/query/ReturnSelect.ts b/src/query/ReturnSelect.ts index bf72595..7605633 100644 --- a/src/query/ReturnSelect.ts +++ b/src/query/ReturnSelect.ts @@ -1,3 +1,5 @@ -export interface ReturnSelect { +import type { Entity } from '../Entity'; + +export interface ReturnSelect { returnSelect: (string & keyof T)[]; } diff --git a/src/query/Sort.ts b/src/query/Sort.ts index 8fa86fe..7eb2b5e 100644 --- a/src/query/Sort.ts +++ b/src/query/Sort.ts @@ -1,7 +1,9 @@ -export type SortString = `${string & keyof T} ASC` | `${string & keyof T} asc` | `${string & keyof T} DESC` | `${string & keyof T} desc` | `${string & keyof T}`; +import type { Entity } from '../Entity'; + +export type SortString = `${string & keyof T} ASC` | `${string & keyof T} asc` | `${string & keyof T} DESC` | `${string & keyof T} desc` | `${string & keyof T}`; type ValidateMultipleSorts< - T, + T extends Entity, TNextSortPart extends string, TPreviouslyValidatedSortString extends string, TSortString extends string @@ -11,19 +13,19 @@ type ValidateMultipleSorts< : ValidateMultipleSorts : `${TPreviouslyValidatedSortString}, ${SortString}`; -export type MultipleSortString = TSortString extends `${SortString}${infer TRestSortPart}` +export type MultipleSortString = TSortString extends `${SortString}${infer TRestSortPart}` ? TRestSortPart extends '' ? TSortString : ValidateMultipleSorts : SortString; -export type SortObject = { +export type SortObject = { [K in keyof T]: -1 | 'asc' | 'desc' | 1; }; -export type Sort = MultipleSortString | SortObject; +export type Sort = MultipleSortString | SortObject; -export interface OrderBy { +export interface OrderBy { propertyName: string & keyof T; descending?: boolean; } diff --git a/src/query/WhereQuery.ts b/src/query/WhereQuery.ts index 85d207a..32ccd18 100644 --- a/src/query/WhereQuery.ts +++ b/src/query/WhereQuery.ts @@ -1,8 +1,8 @@ -import type { EntityFieldValue } from '../Entity'; +import type { EntityFieldValue, Entity } from '../Entity'; -export type WhereClauseValue = EntityFieldValue | WhereQuery; +export type WhereClauseValue = EntityFieldValue | WhereQuery; -export type WhereQuery = { +export type WhereQuery = { [P in keyof T]?: WhereClauseValue; } & { or?: WhereQuery[]; diff --git a/src/types/GetPropertyType.ts b/src/types/GetPropertyType.ts deleted file mode 100644 index eebbbb3..0000000 --- a/src/types/GetPropertyType.ts +++ /dev/null @@ -1 +0,0 @@ -export type GetPropertyType = T[TProperty] extends unknown[] ? T[TProperty][0] : T[TProperty]; diff --git a/src/types/GetValueType.ts b/src/types/GetValueType.ts new file mode 100644 index 0000000..01845bb --- /dev/null +++ b/src/types/GetValueType.ts @@ -0,0 +1 @@ +export type GetValueType = T extends TValueType[] ? (T extends (infer U)[] ? U : never) : T extends TValueType ? T : never; diff --git a/src/types/IsValueOfType.ts b/src/types/IsValueOfType.ts new file mode 100644 index 0000000..5cc93d8 --- /dev/null +++ b/src/types/IsValueOfType.ts @@ -0,0 +1 @@ +export type IsValueOfType = T extends TValueType | TValueType[] ? K : never; diff --git a/src/types/PickByValueType.ts b/src/types/PickByValueType.ts new file mode 100644 index 0000000..3d21399 --- /dev/null +++ b/src/types/PickByValueType.ts @@ -0,0 +1,6 @@ +import type { GetValueType } from './GetValueType'; +import type { IsValueOfType } from './IsValueOfType'; + +export type PickByValueType = { + [K in keyof T as IsValueOfType]: GetValueType; +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..a12a0fe --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './GetValueType'; +export * from './IsValueOfType'; +export * from './PickByValueType'; diff --git a/tests/models/Category.ts b/tests/models/Category.ts index 3a32530..074298d 100644 --- a/tests/models/Category.ts +++ b/tests/models/Category.ts @@ -1,4 +1,3 @@ -import type { Entity } from '../../src'; import { column, table } from '../../src/decorators'; import { ModelBase } from './ModelBase'; @@ -10,7 +9,7 @@ import { ProductCategory } from './ProductCategory'; @table({ name: 'categories', }) -export class Category extends ModelBase implements Entity { +export class Category extends ModelBase { @column({ type: 'string', required: true, diff --git a/tests/models/KitchenSink.ts b/tests/models/KitchenSink.ts index ac72207..835eed1 100644 --- a/tests/models/KitchenSink.ts +++ b/tests/models/KitchenSink.ts @@ -1,4 +1,3 @@ -import type { Entity } from '../../src'; import { column, table } from '../../src/decorators'; import { ModelBase } from './ModelBase'; @@ -6,7 +5,7 @@ import { ModelBase } from './ModelBase'; @table({ name: 'kitchen_sink', }) -export class KitchenSink extends ModelBase implements Entity { +export class KitchenSink extends ModelBase { @column({ type: 'string', required: true, @@ -36,4 +35,8 @@ export class KitchenSink extends ModelBase implements Entity { name: 'string_array_column', }) public stringArrayColumn?: string[]; + + public instanceFunction(): string { + return `${this.name || ''} bar!`; + } } diff --git a/tests/models/ModelBase.ts b/tests/models/ModelBase.ts index 10fe6f7..659fd77 100644 --- a/tests/models/ModelBase.ts +++ b/tests/models/ModelBase.ts @@ -1,6 +1,7 @@ +import { Entity } from '../../src'; import { primaryColumn } from '../../src/decorators'; -export abstract class ModelBase { +export abstract class ModelBase extends Entity { @primaryColumn({ type: 'integer' }) public id!: number; } diff --git a/tests/models/NonStandardPrimaryId.ts b/tests/models/NonStandardPrimaryId.ts new file mode 100644 index 0000000..53de9ff --- /dev/null +++ b/tests/models/NonStandardPrimaryId.ts @@ -0,0 +1,18 @@ +import { Entity } from '../../src'; +import { column, primaryColumn, table } from '../../src/decorators'; + +@table({ + name: 'non_standard_primary_id', +}) +export class NonStandardPrimaryId extends Entity { + @primaryColumn({ + type: 'string', + name: 'unique_id', + }) + public uniqueId!: string; + + @column({ + type: 'string', + }) + public foo?: string; +} diff --git a/tests/models/Product.ts b/tests/models/Product.ts index f65017e..3334c09 100644 --- a/tests/models/Product.ts +++ b/tests/models/Product.ts @@ -1,4 +1,3 @@ -import type { Entity } from '../../src'; import { column, table } from '../../src/decorators'; // eslint-disable-next-line import/no-cycle @@ -12,7 +11,7 @@ import type { Store } from './Store'; @table({ name: 'products', }) -export class Product extends ModelBase implements Entity { +export class Product extends ModelBase { @column({ type: 'string', required: true, diff --git a/tests/models/ProductCategory.ts b/tests/models/ProductCategory.ts index dce5333..dd0b4fc 100644 --- a/tests/models/ProductCategory.ts +++ b/tests/models/ProductCategory.ts @@ -1,4 +1,4 @@ -import type { Entity } from '../../src'; +import { Entity } from '../../src'; import { column, primaryColumn, table } from '../../src/decorators'; // eslint-disable-next-line import/no-cycle @@ -9,7 +9,7 @@ import { Product } from './Product'; @table({ name: 'product__category', }) -export class ProductCategory implements Entity { +export class ProductCategory extends Entity { @primaryColumn({ type: 'integer' }) public id!: number; diff --git a/tests/models/RequiredPropertyWithDefaultValue.ts b/tests/models/RequiredPropertyWithDefaultValue.ts new file mode 100644 index 0000000..d28c27c --- /dev/null +++ b/tests/models/RequiredPropertyWithDefaultValue.ts @@ -0,0 +1,20 @@ +import { column, table } from '../../src/decorators'; + +import { ModelBase } from './ModelBase'; + +@table({ + name: 'some_table', +}) +export class RequiredPropertyWithDefaultValue extends ModelBase { + @column({ + type: 'string', + required: true, + defaultsTo: 'foobar', + }) + public foo!: string; + + @column({ + type: 'string', + }) + public bar?: string; +} diff --git a/tests/models/RequiredPropertyWithDefaultValueFunction.ts b/tests/models/RequiredPropertyWithDefaultValueFunction.ts new file mode 100644 index 0000000..8b53d08 --- /dev/null +++ b/tests/models/RequiredPropertyWithDefaultValueFunction.ts @@ -0,0 +1,20 @@ +import { column, table } from '../../src/decorators'; + +import { ModelBase } from './ModelBase'; + +@table({ + name: 'some_other_table', +}) +export class RequiredPropertyWithDefaultValueFunction extends ModelBase { + @column({ + type: 'string', + required: true, + defaultsTo: () => 'foobar', + }) + public foo!: string; + + @column({ + type: 'string', + }) + public bar?: string; +} diff --git a/tests/models/SimpleWithCollections.ts b/tests/models/SimpleWithCollections.ts new file mode 100644 index 0000000..71be0d9 --- /dev/null +++ b/tests/models/SimpleWithCollections.ts @@ -0,0 +1,30 @@ +import { column, table } from '../../src/decorators'; + +import { Category } from './Category'; +import { ModelBase } from './ModelBase'; +import { Product } from './Product'; +import { ProductCategory } from './ProductCategory'; + +@table({ + name: 'simple', +}) +export class SimpleWithCollections extends ModelBase { + @column({ + type: 'string', + required: true, + }) + public name!: string; + + @column({ + collection: () => Product.name, + via: 'store', + }) + public products?: Product[]; + + @column({ + collection: () => Category.name, + through: () => ProductCategory.name, + via: 'product', + }) + public categories!: Category[]; +} diff --git a/tests/models/SimpleWithCreatedAt.ts b/tests/models/SimpleWithCreatedAt.ts new file mode 100644 index 0000000..de7b135 --- /dev/null +++ b/tests/models/SimpleWithCreatedAt.ts @@ -0,0 +1,17 @@ +import { column, createDateColumn, table } from '../../src/decorators'; + +import { ModelBase } from './ModelBase'; + +@table({ + name: 'simple', +}) +export class SimpleWithCreatedAt extends ModelBase { + @column({ + type: 'string', + required: true, + }) + public name!: string; + + @createDateColumn() + public createdAt!: Date; +} diff --git a/tests/models/SimpleWithCreatedAtAndUpdatedAt.ts b/tests/models/SimpleWithCreatedAtAndUpdatedAt.ts new file mode 100644 index 0000000..de7bece --- /dev/null +++ b/tests/models/SimpleWithCreatedAtAndUpdatedAt.ts @@ -0,0 +1,8 @@ +import { updateDateColumn } from '../../src/decorators'; + +import { SimpleWithCreatedAt } from './SimpleWithCreatedAt'; + +export class SimpleWithCreatedAtAndUpdatedAt extends SimpleWithCreatedAt { + @updateDateColumn() + public updatedAt!: Date; +} diff --git a/tests/models/SimpleWithJson.ts b/tests/models/SimpleWithJson.ts new file mode 100644 index 0000000..4786a76 --- /dev/null +++ b/tests/models/SimpleWithJson.ts @@ -0,0 +1,19 @@ +import { column, table } from '../../src/decorators'; + +import { ModelBase } from './ModelBase'; + +@table({ + name: 'simple', +}) +export class SimpleWithJson extends ModelBase { + @column({ + type: 'string', + required: true, + }) + public name!: string; + + @column({ + type: 'json', + }) + public bar?: unknown; +} diff --git a/tests/models/SimpleWithUpdatedAt.ts b/tests/models/SimpleWithUpdatedAt.ts new file mode 100644 index 0000000..18e3f10 --- /dev/null +++ b/tests/models/SimpleWithUpdatedAt.ts @@ -0,0 +1,17 @@ +import { column, table, updateDateColumn } from '../../src/decorators'; + +import { ModelBase } from './ModelBase'; + +@table({ + name: 'simple', +}) +export class SimpleWithUpdatedAt extends ModelBase { + @column({ + type: 'string', + required: true, + }) + public name!: string; + + @updateDateColumn() + public updatedAt!: Date; +} diff --git a/tests/models/Store.ts b/tests/models/Store.ts index d7a1ef8..fb03376 100644 --- a/tests/models/Store.ts +++ b/tests/models/Store.ts @@ -1,4 +1,3 @@ -import type { Entity } from '../../src'; import { column, table } from '../../src/decorators'; import { ModelBase } from './ModelBase'; @@ -8,7 +7,7 @@ import { Product } from './Product'; @table({ name: 'stores', }) -export class Store extends ModelBase implements Entity { +export class Store extends ModelBase { @column({ type: 'string', }) diff --git a/tests/models/index.ts b/tests/models/index.ts index 6dae836..67a0a77 100644 --- a/tests/models/index.ts +++ b/tests/models/index.ts @@ -1,8 +1,16 @@ export * from './Category'; export * from './KitchenSink'; +export * from './NonStandardPrimaryId'; export * from './Product'; export * from './ProductCategory'; export * from './ProductWithCreatedAt'; export * from './ProductWithCreateUpdateDateTracking'; export * from './ReadonlyProduct'; +export * from './RequiredPropertyWithDefaultValue'; +export * from './RequiredPropertyWithDefaultValueFunction'; +export * from './SimpleWithCollections'; +export * from './SimpleWithCreatedAt'; +export * from './SimpleWithCreatedAtAndUpdatedAt'; +export * from './SimpleWithJson'; +export * from './SimpleWithUpdatedAt'; export * from './Store'; diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index 84c3dbd..38184a5 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -5,13 +5,10 @@ import type { QueryResult } from 'pg'; import { Pool } from 'postgres-pool'; import { anyString, anything, capture, instance, mock, reset, verify, when } from 'ts-mockito'; -import type { Entity, IReadonlyRepository, IRepository, Repository } from '../src'; -import { ReadonlyRepository, initialize } from '../src'; -import { ColumnTypeMetadata, ModelMetadata } from '../src/metadata'; +import type { Repository, ReadonlyRepository } from '../src'; +import { initialize } from '../src'; -import { Category, Product, ProductCategory, ReadonlyProduct, Store } from './models'; - -type RepositoriesByModelNameLowered = Record | IRepository>; +import { Category, KitchenSink, Product, ProductCategory, ReadonlyProduct, Store } from './models'; function getQueryResult(rows: T[] = []): QueryResult { return { @@ -31,21 +28,21 @@ describe('ReadonlyRepository', () => { // eslint-disable-next-line @typescript-eslint/naming-convention let ReadonlyProductRepository: ReadonlyRepository; // eslint-disable-next-line @typescript-eslint/naming-convention + let ReadonlyKitchenSinkRepository: ReadonlyRepository; + // eslint-disable-next-line @typescript-eslint/naming-convention let StoreRepository: Repository; - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class TestEntity implements Entity {} - before(() => { should = chai.should(); const repositoriesByModelName = initialize({ - models: [Category, Product, ProductCategory, ReadonlyProduct, Store], + models: [Category, KitchenSink, Product, ProductCategory, ReadonlyProduct, Store], pool: instance(mockedPool), }); ProductRepository = repositoriesByModelName.Product as Repository; ReadonlyProductRepository = repositoriesByModelName.ReadonlyProduct as ReadonlyRepository; + ReadonlyKitchenSinkRepository = repositoriesByModelName.KitchenSink as ReadonlyRepository; StoreRepository = repositoriesByModelName.Store as Repository; }); @@ -165,361 +162,200 @@ describe('ReadonlyRepository', () => { }); describe('Parse number columns', () => { it('should parse integer columns from integer query value', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'integer', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const numberValue = 42; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: `${numberValue}`, + name, + intColumn: `${numberValue}`, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: numberValue, + name, + intColumn: numberValue, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); it('should parse integer columns from float strings query value', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'integer', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const numberValue = 42.24; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: `${numberValue}`, + name, + intColumn: `${numberValue}`, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: 42, + name, + intColumn: 42, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); it('should parse integer columns that return as number', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'integer', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const numberValue = 42; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: numberValue, + name, + intColumn: numberValue, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: numberValue, + name, + intColumn: numberValue, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); it('should ignore large integer columns values', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'integer', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const largeNumberValue = `${Number.MAX_SAFE_INTEGER}0`; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: largeNumberValue, + name, + intColumn: largeNumberValue, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: largeNumberValue, + name, + intColumn: largeNumberValue, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); it('should parse float columns return as float strings', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'float', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const numberValue = 42.24; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: `${numberValue}`, + name, + floatColumn: `${numberValue}`, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: numberValue, + name, + floatColumn: numberValue, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); it('should parse float columns return as number', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'float', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const numberValue = 42.24; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: numberValue, + name, + floatColumn: numberValue, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: numberValue, + name, + floatColumn: numberValue, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); it('should ignore large float columns', async () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'float', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - const id = faker.random.number(); + const name = faker.random.uuid(); const largeNumberValue = `${Number.MAX_SAFE_INTEGER}0.42`; when(mockedPool.query(anyString(), anything())).thenResolve( getQueryResult([ { id, - foo: largeNumberValue, + name, + floatColumn: largeNumberValue, }, ]), ); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); should.exist(result); result!.should.deep.equal({ id, - foo: largeNumberValue, + name, + floatColumn: largeNumberValue, }); const [query, params] = capture(mockedPool.query).first(); - query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); + 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([]); }); }); @@ -557,6 +393,43 @@ describe('ReadonlyRepository', () => { storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1 LIMIT 1'); storeQueryParams!.should.deep.equal([store.id]); }); + it('should support populating a single relation with partial select and order', async () => { + const store = { + id: faker.random.number(), + name: `store - ${faker.random.uuid()}`, + }; + const product = { + id: faker.random.number(), + name: `product - ${faker.random.uuid()}`, + store, + }; + + when(mockedPool.query(anyString(), anything())).thenResolve( + getQueryResult([ + { + id: product.id, + name: product.name, + store: store.id, + }, + ]), + getQueryResult([store]), + ); + + const result = await ProductRepository.findOne().populate('store', { + select: ['name'], + sort: 'name', + }); + verify(mockedPool.query(anyString(), anything())).twice(); + should.exist(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'); + 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" LIMIT 1'); + storeQueryParams!.should.deep.equal([store.id]); + }); it('should support populating collection', async () => { const store = { id: faker.random.number(), @@ -594,6 +467,46 @@ describe('ReadonlyRepository', () => { 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]); }); + it('should support populating collection with partial select and order', async () => { + const store = { + id: faker.random.number(), + name: `store - ${faker.random.uuid()}`, + }; + const product1 = { + id: faker.random.number(), + name: `product - ${faker.random.uuid()}`, + store: store.id, + }; + const product2 = { + id: faker.random.number(), + name: `product - ${faker.random.uuid()}`, + store: store.id, + }; + + const storeWithProducts = _.extend( + { + products: [product1, product2], + }, + store, + ); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([store]), getQueryResult([product1, product2])); + + const result = await StoreRepository.findOne().populate('products', { + select: ['name'], + sort: 'aliases', + }); + verify(mockedPool.query(anyString(), anything())).twice(); + should.exist(result); + result!.should.deep.equal(storeWithProducts); + + const [storeQuery, storeQueryParams] = capture(mockedPool.query).first(); + storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); + 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]); + }); it('should support populating multi-multi collection', async () => { const product = { id: faker.random.number(), @@ -642,6 +555,57 @@ describe('ReadonlyRepository', () => { categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); 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.random.number(), + name: `product - ${faker.random.uuid()}`, + }; + const category1 = { + id: faker.random.number(), + name: `category - ${faker.random.uuid()}`, + }; + const category2 = { + id: faker.random.number(), + name: `category - ${faker.random.uuid()}`, + }; + const productCategory1Map = { + id: faker.random.number(), + product: product.id, + category: category1.id, + }; + const productCategory2Map = { + id: faker.random.number(), + product: product.id, + category: category2.id, + }; + + const productWithCategories = _.extend( + { + categories: [category1, category2], + }, + product, + ); + + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product]), getQueryResult([productCategory1Map, productCategory2Map]), getQueryResult([category1, category2])); + + const result = await ProductRepository.findOne().populate('categories', { + select: ['name'], + sort: 'name desc', + }); + verify(mockedPool.query(anyString(), anything())).thrice(); + should.exist(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'); + productQueryParams!.should.deep.equal([]); + const [productCategoryMapQuery, productCategoryMapQueryParams] = capture(mockedPool.query).second(); + productCategoryMapQuery.should.equal('SELECT "category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); + 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]]); + }); it('should support complex query with multiple chained modifiers', async () => { const store = { id: faker.random.number(), @@ -725,110 +689,27 @@ describe('ReadonlyRepository', () => { categoryQueryParams!.should.deep.equal([[category1.id, category2.id], 'category%']); }); it('should have instance functions be equal across multiple queries', async () => { - // eslint-disable-next-line max-classes-per-file - class TestEntityWithInstanceFunction implements Entity { - public id!: number; - - public foo: string | undefined; - - public toBar(): string { - return `${this.foo || ''} bar!`; - } - } - - const model = new ModelMetadata({ - name: 'foo', - type: TestEntityWithInstanceFunction, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - - const id = faker.random.number(); - const foo = faker.random.uuid(); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id, - foo, - }, - ]), - ); + const result = { + id: faker.random.number(), + name: `sink - ${faker.random.uuid()}`, + }; + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([result])); - const result1 = await repository.findOne(); - const result2 = await repository.findOne(); + const result1 = await ReadonlyKitchenSinkRepository.findOne(); + const result2 = await ReadonlyKitchenSinkRepository.findOne(); verify(mockedPool.query(anyString(), anything())).twice(); should.exist(result1); result1!.should.deep.equal(result2); - result1!.toBar().should.equal(`${foo} bar!`); + result1!.instanceFunction().should.equal(`${result.name} bar!`); should.exist(result2); - result2!.toBar().should.equal(`${foo} bar!`); + result2!.instanceFunction().should.equal(`${result.name} bar!`); }); it('should not create an object/assign instance functions to null results', async () => { - // eslint-disable-next-line max-classes-per-file - class TestEntityWithInstanceFunction implements Entity { - public id!: number; - - public foo: string | undefined; - - public toBar(): string { - return `${this.foo} bar!`; - } - } - - const model = new ModelMetadata({ - name: 'foo', - type: TestEntityWithInstanceFunction, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([null])); - const result = await repository.findOne(); + const result = await ReadonlyKitchenSinkRepository.findOne(); verify(mockedPool.query(anyString(), anything())).once(); @@ -1167,64 +1048,20 @@ describe('ReadonlyRepository', () => { params!.should.deep.equal([store.id]); }); it('should have instance functions be equal across multiple queries', async () => { - // eslint-disable-next-line max-classes-per-file - class TestEntityWithInstanceFunction implements Entity { - public id!: number; - - public foo: string | undefined; - - public toBar(): string { - return `${this.foo} bar!`; - } - } - - const model = new ModelMetadata({ - name: 'foo', - type: TestEntityWithInstanceFunction, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'foo', - propertyName: 'foo', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - const repository = new ReadonlyRepository({ - modelMetadata: model, - type: model.type, - pool: instance(mockedPool), - repositoriesByModelNameLowered: repositories, - }); - repositories[model.name.toLowerCase()] = repository; - - const id = faker.random.number(); - const foo = faker.random.uuid(); - when(mockedPool.query(anyString(), anything())).thenResolve( - getQueryResult([ - { - id, - foo, - }, - ]), - ); + const result = { + id: faker.random.number(), + name: `sink - ${faker.random.uuid()}`, + }; + when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([result])); - const result1 = await repository.find(); - const result2 = await repository.find(); + const result1 = await ReadonlyKitchenSinkRepository.find(); + const result2 = await ReadonlyKitchenSinkRepository.find(); verify(mockedPool.query(anyString(), anything())).twice(); should.exist(result1); should.exist(result2); result1.should.deep.equal(result2); - result1[0].toBar().should.equal(`${foo} bar!`); - result2[0].toBar().should.equal(`${foo} bar!`); + result1[0].instanceFunction().should.equal(`${result.name} bar!`); + result2[0].instanceFunction().should.equal(`${result.name} bar!`); }); }); describe('#count()', () => { diff --git a/tests/repository.tests.ts b/tests/repository.tests.ts index b8fe65c..b78a251 100644 --- a/tests/repository.tests.ts +++ b/tests/repository.tests.ts @@ -5,8 +5,8 @@ import type { QueryResult } from 'pg'; import { Pool } from 'postgres-pool'; import { anyString, anything, capture, instance, mock, reset, verify, when } from 'ts-mockito'; -import type { Entity, IReadonlyRepository, IRepository } from '../src'; -import { Repository, initialize } from '../src'; +import type { IReadonlyRepository, IRepository } from '../src'; +import { Repository, initialize, Entity } from '../src'; import { ColumnTypeMetadata, ModelMetadata } from '../src/metadata'; import { Product, ProductWithCreateUpdateDateTracking, Store } from './models'; @@ -32,7 +32,7 @@ describe('Repository', () => { let ProductWithCreateUpdateDateTrackingRepository: Repository; // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class TestEntity implements Entity {} + class TestEntity extends Entity {} before(() => { should = chai.should(); diff --git a/tests/sqlHelper.tests.ts b/tests/sqlHelper.tests.ts index 427291d..4f63f2e 100644 --- a/tests/sqlHelper.tests.ts +++ b/tests/sqlHelper.tests.ts @@ -3,31 +3,64 @@ import * as faker from 'faker'; import { Pool } from 'postgres-pool'; import { mock } from 'ts-mockito'; -import type { Entity, IReadonlyRepository, IRepository } from '../src'; -import { Repository, initialize } from '../src'; -import { ColumnCollectionMetadata, ColumnTypeMetadata, ModelMetadata } from '../src/metadata'; +import type { IReadonlyRepository, IRepository, Entity } from '../src'; +import { initialize } from '../src'; +import type { ModelMetadata } from '../src/metadata'; import * as sqlHelper from '../src/SqlHelper'; -import { Category, Product, ProductCategory, ProductWithCreateUpdateDateTracking, ProductWithCreatedAt, ReadonlyProduct, Store, KitchenSink } from './models'; +import { + Category, + Product, + ProductCategory, + ProductWithCreateUpdateDateTracking, + ProductWithCreatedAt, + ReadonlyProduct, + Store, + KitchenSink, + NonStandardPrimaryId, + RequiredPropertyWithDefaultValue, + RequiredPropertyWithDefaultValueFunction, + SimpleWithCollections, + SimpleWithCreatedAt, + SimpleWithCreatedAtAndUpdatedAt, + SimpleWithJson, + SimpleWithUpdatedAt, +} from './models'; type RepositoriesByModelNameLowered = Record | IRepository>; describe('sqlHelper', () => { let should: Chai.Should; const mockedPool: Pool = mock(Pool); + let repositoriesByModelName: RepositoriesByModelNameLowered; const repositoriesByModelNameLowered: RepositoriesByModelNameLowered = {}; - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class TestEntity implements Entity {} - before(() => { should = chai.should(); - const repositoriesByModelName = initialize({ - models: [Category, KitchenSink, Product, ProductCategory, ProductWithCreatedAt, ProductWithCreateUpdateDateTracking, ReadonlyProduct, Store], + repositoriesByModelName = initialize({ + models: [ + Category, + KitchenSink, + NonStandardPrimaryId, + Product, + ProductCategory, + ProductWithCreatedAt, + ProductWithCreateUpdateDateTracking, + ReadonlyProduct, + RequiredPropertyWithDefaultValue, + RequiredPropertyWithDefaultValueFunction, + SimpleWithCollections, + SimpleWithCreatedAt, + SimpleWithCreatedAtAndUpdatedAt, + SimpleWithJson, + SimpleWithUpdatedAt, + Store, + ], pool: mockedPool, }); for (const [modelName, repository] of Object.entries(repositoriesByModelName)) { + repositoriesByModelName[modelName] = repository; repositoriesByModelNameLowered[modelName.toLowerCase()] = repository; } }); @@ -37,7 +70,7 @@ describe('sqlHelper', () => { it('should include all columns if select is undefined', () => { const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: {}, sorts: [], limit: 1, @@ -52,7 +85,7 @@ describe('sqlHelper', () => { it('should include primaryKey column if select is empty', () => { const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, select: [], where: {}, sorts: [], @@ -64,81 +97,9 @@ describe('sqlHelper', () => { params.should.deep.equal([]); }); it('should include non "id" primaryKey column if select is empty', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'foobario', - propertyName: 'foobario', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const { query, params } = sqlHelper.getSelectQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, - select: [], - where: {}, - sorts: [], - limit: 1, - skip: 0, - }); - - query.should.equal(`SELECT "foobario" FROM "${model.tableName}" LIMIT 1`); - params.should.deep.equal([]); - }); - it('should include non "id" primaryKey column name with id propertyName if select is empty', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'foobario', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - - const { query, params } = sqlHelper.getSelectQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelNameLowered.nonstandardprimaryid.model, select: [], where: {}, sorts: [], @@ -146,13 +107,13 @@ describe('sqlHelper', () => { skip: 0, }); - query.should.equal(`SELECT "foobario" AS "id" FROM "${model.tableName}" LIMIT 1`); + query.should.equal(`SELECT "unique_id" AS "uniqueId" FROM "${repositoriesByModelName.NonStandardPrimaryId.model.tableName}" LIMIT 1`); params.should.deep.equal([]); }); it('should include primaryKey column if select does not include it', () => { - const { query, params } = sqlHelper.getSelectQueryAndParams({ + const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, select: ['name'], where: {}, sorts: [], @@ -164,98 +125,26 @@ describe('sqlHelper', () => { params.should.deep.equal([]); }); it('should include non "id" primaryKey column if select does not include it', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'foobario', - propertyName: 'foobario', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - - const { query, params } = sqlHelper.getSelectQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, - select: ['name'], - where: {}, - sorts: [], - limit: 1, - skip: 0, - }); - - query.should.equal(`SELECT "name","foobario" FROM "${model.tableName}" LIMIT 1`); - params.should.deep.equal([]); - }); - it('should include non "id" primaryKey column with id propertyName if select does not include it', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'foobario', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - - const { query, params } = sqlHelper.getSelectQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, - select: ['name'], + const { query, params } = sqlHelper.getSelectQueryAndParams({ + repositoriesByModelNameLowered, + model: repositoriesByModelNameLowered.nonstandardprimaryid.model as ModelMetadata, + select: ['foo'], where: {}, sorts: [], limit: 1, skip: 0, }); - query.should.equal(`SELECT "name","foobario" AS "id" FROM "${model.tableName}" LIMIT 1`); + query.should.equal(`SELECT "foo","unique_id" AS "uniqueId" FROM "${repositoriesByModelName.NonStandardPrimaryId.model.tableName}" LIMIT 1`); params.should.deep.equal([]); }); }); describe('where', () => { it('should include where statement if defined', () => { const name = faker.random.uuid(); - const { query, params } = sqlHelper.getSelectQueryAndParams({ + const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: { name, }, @@ -272,9 +161,9 @@ describe('sqlHelper', () => { }); describe('sorts', () => { it('should include order by statement if defined', () => { - const { query, params } = sqlHelper.getSelectQueryAndParams({ + const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, sorts: [ { propertyName: 'name', @@ -295,7 +184,7 @@ describe('sqlHelper', () => { it('should include OFFSET statement if skip is a number', () => { const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: {}, sorts: [], limit: 1, @@ -312,7 +201,7 @@ describe('sqlHelper', () => { it('should include LIMIT statement if limit is a number', () => { const { query, params } = sqlHelper.getSelectQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: {}, sorts: [], skip: 0, @@ -330,7 +219,7 @@ describe('sqlHelper', () => { it('should count all records if no where statement is defined', () => { const { query, params } = sqlHelper.getCountQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, }); query.should.equal(`SELECT count(*) AS "count" FROM "${repositoriesByModelNameLowered.product.model.tableName}"`); @@ -342,9 +231,9 @@ describe('sqlHelper', () => { name: `store - ${faker.random.uuid()}`, }; - const { query, params } = sqlHelper.getCountQueryAndParams({ + const { query, params } = sqlHelper.getCountQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { store, }, @@ -359,54 +248,19 @@ describe('sqlHelper', () => { ((): void => { sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: { - store: faker.random.uuid(), + store: faker.random.number(), }, returnRecords: true, }); }).should.throw(Error, `Create statement for "${repositoriesByModelNameLowered.product.model.name}" is missing value for required field: name`); }); it('should not throw if a required property has a defaultValue and an undefined initial value', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'bar', - propertyName: 'bar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - ((): void => { sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.RequiredPropertyWithDefaultValue.model as ModelMetadata, values: { bar: faker.random.uuid(), }, @@ -415,85 +269,23 @@ describe('sqlHelper', () => { }).should.not.throw(); }); it('should not override properties with defaultValue if value is defined', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - - const name = faker.random.uuid(); + const value = faker.random.uuid(); const { params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.RequiredPropertyWithDefaultValue.model as ModelMetadata, values: { - name, + foo: value, }, returnRecords: true, }); - params.should.deep.equal([name]); + params.should.deep.equal([value]); }); it('should set undefined properties to defaultValue if defined on schema', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - defaultsTo: 'foobar', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'bar', - propertyName: 'bar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const bar = faker.random.uuid(); const { params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.RequiredPropertyWithDefaultValue.model as ModelMetadata, values: { bar, }, @@ -502,44 +294,10 @@ describe('sqlHelper', () => { params.should.deep.equal(['foobar', bar]); }); it('should set undefined properties to result of defaultValue function if defined on schema', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - defaultsTo: (): string => 'foobar', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'bar', - propertyName: 'bar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const bar = faker.random.uuid(); const { params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.RequiredPropertyWithDefaultValueFunction.model as ModelMetadata, values: { bar, }, @@ -548,51 +306,17 @@ describe('sqlHelper', () => { params.should.deep.equal(['foobar', bar]); }); it('should set createdAt if schema.autoCreatedAt and value is undefined', () => { - const beforeTime = new Date(); - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'created_at', - propertyName: 'createdAt', - type: 'datetime', - createDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); + const beforeTime = new Date(); const { query, params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithCreatedAt.model as ModelMetadata, values: { name, }, }); - query.should.equal(`INSERT INTO "${model.tableName}" ("name","created_at") VALUES ($1,$2) RETURNING "id","name","created_at" AS "createdAt"`); + query.should.equal(`INSERT INTO "${repositoriesByModelName.SimpleWithCreatedAt.model.tableName}" ("name","created_at") VALUES ($1,$2) RETURNING "id","name","created_at" AS "createdAt"`); params.should.have.length(2); const afterTime = new Date(); for (const [index, value] of params.entries()) { @@ -606,99 +330,31 @@ describe('sqlHelper', () => { }); it('should not override createdAt if schema.autoCreatedAt and value is defined', () => { const createdAt = faker.date.past(); - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'created_at', - propertyName: 'createdAt', - type: 'datetime', - createDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithCreatedAt.model as ModelMetadata, values: { name, createdAt, }, }); - query.should.equal(`INSERT INTO "${model.tableName}" ("name","created_at") VALUES ($1,$2) RETURNING "id","name","created_at" AS "createdAt"`); + query.should.equal(`INSERT INTO "${repositoriesByModelName.SimpleWithCreatedAt.model.tableName}" ("name","created_at") VALUES ($1,$2) RETURNING "id","name","created_at" AS "createdAt"`); params.should.deep.equal([name, createdAt]); }); it('should set updatedAt if schema.autoUpdatedAt and value is undefined', () => { const beforeTime = new Date(); - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'updated_at', - propertyName: 'updatedAt', - type: 'datetime', - updateDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithUpdatedAt.model as ModelMetadata, values: { name, }, }); - query.should.equal(`INSERT INTO "${model.tableName}" ("name","updated_at") VALUES ($1,$2) RETURNING "id","name","updated_at" AS "updatedAt"`); + query.should.equal(`INSERT INTO "${repositoriesByModelName.SimpleWithUpdatedAt.model.tableName}" ("name","updated_at") VALUES ($1,$2) RETURNING "id","name","updated_at" AS "updatedAt"`); params.should.have.length(2); const afterTime = new Date(); for (const [index, value] of params.entries()) { @@ -712,120 +368,48 @@ describe('sqlHelper', () => { }); it('should not override updatedAt if schema.autoUpdatedAt and value is defined', () => { const updatedAt = faker.date.past(); - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'updated_at', - propertyName: 'updatedAt', - type: 'datetime', - updateDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithUpdatedAt.model as ModelMetadata, values: { name, updatedAt, }, }); - query.should.equal(`INSERT INTO "${model.tableName}" ("name","updated_at") VALUES ($1,$2) RETURNING "id","name","updated_at" AS "updatedAt"`); + query.should.equal(`INSERT INTO "${repositoriesByModelName.SimpleWithUpdatedAt.model.tableName}" ("name","updated_at") VALUES ($1,$2) RETURNING "id","name","updated_at" AS "updatedAt"`); params.should.deep.equal([name, updatedAt]); }); it('should ignore collection properties', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnCollectionMetadata({ - target: 'foo', - name: 'bars', - propertyName: 'bars', - collection: 'bar', - via: 'foo', - }), - new ColumnCollectionMetadata({ - target: 'foo', - name: 'bats', - propertyName: 'bats', - collection: 'bats', - through: 'foo__bats', - via: 'foo', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); + const product = new Product(); + product.id = faker.random.number(); + const category = new Category(); + category.id = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithCollections.model as ModelMetadata, values: { name, - bars: [faker.random.uuid()], - bats: [faker.random.uuid()], + products: [product], + categories: [category], }, }); - query.should.equal(`INSERT INTO "${model.tableName}" ("name") VALUES ($1) RETURNING "id","name"`); + query.should.equal(`INSERT INTO "${repositoriesByModelName.SimpleWithCollections.model.tableName}" ("name") VALUES ($1) RETURNING "id","name"`); params.should.deep.equal([name]); }); it('should use primaryKey value if hydrated object is passed as a value', () => { - const store = { - id: faker.random.uuid(), - name: `store - ${faker.random.uuid()}`, - }; + const store = new Store(); + store.id = faker.random.number(); + store.name = `store - ${faker.random.uuid()}`; const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: { name, store, @@ -839,39 +423,6 @@ describe('sqlHelper', () => { }); it('should cast value to jsonb if type=json and value is an array', () => { // Please see https://github.com/brianc/node-postgres/issues/442 for details of why this is needed - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'bar', - propertyName: 'bar', - type: 'json', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const bar = [ { @@ -880,23 +431,23 @@ describe('sqlHelper', () => { ]; const { query, params } = sqlHelper.getInsertQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithJson.model as ModelMetadata, values: { name, bar, }, }); - query.should.equal(`INSERT INTO "${model.tableName}" ("name","bar") VALUES ($1,$2::jsonb) RETURNING "id","name","bar"`); + query.should.equal(`INSERT INTO "${repositoriesByModelName.SimpleWithJson.model.tableName}" ("name","bar") VALUES ($1,$2::jsonb) RETURNING "id","name","bar"`); params.should.deep.equal([name, JSON.stringify(bar)]); }); it('should support inserting a single record and return records if returnRecords=true', () => { - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: { name, store: storeId, @@ -910,11 +461,11 @@ describe('sqlHelper', () => { params.should.deep.equal([name, [], storeId]); }); it('should support inserting a single record and return specific columns for records, if returnRecords=true and returnSelect is defined', () => { - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const name = faker.random.uuid(); - const { query, params } = sqlHelper.getInsertQueryAndParams({ + const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: { name, store: storeId, @@ -927,11 +478,11 @@ describe('sqlHelper', () => { params.should.deep.equal([name, [], storeId]); }); it('should support inserting a single record and not return records if returnRecords=false', () => { - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: { name, store: storeId, @@ -943,13 +494,13 @@ describe('sqlHelper', () => { params.should.deep.equal([name, [], storeId]); }); it('should support inserting multiple records and return specific columns for records, if returnRecords=true and returnSelect is defined', () => { - const storeId1 = faker.random.uuid(); + const storeId1 = faker.random.number(); const name1 = faker.random.uuid(); - const storeId2 = faker.random.uuid(); + const storeId2 = faker.random.number(); const name2 = faker.random.uuid(); - const { query, params } = sqlHelper.getInsertQueryAndParams({ + const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: [ { name: name1, @@ -968,13 +519,13 @@ describe('sqlHelper', () => { params.should.deep.equal([name1, name2, [], [], storeId1, storeId2]); }); it('should support inserting multiple records and return records if returnRecords=true', () => { - const storeId1 = faker.random.uuid(); + const storeId1 = faker.random.number(); const name1 = faker.random.uuid(); - const storeId2 = faker.random.uuid(); + const storeId2 = faker.random.number(); const name2 = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: [ { name: name1, @@ -993,13 +544,13 @@ describe('sqlHelper', () => { params.should.deep.equal([name1, name2, [], [], storeId1, storeId2]); }); it('should support inserting multiple records and not return records if returnRecords=false', () => { - const storeId1 = faker.random.uuid(); + const storeId1 = faker.random.number(); const name1 = faker.random.uuid(); - const storeId2 = faker.random.uuid(); + const storeId2 = faker.random.number(); const name2 = faker.random.uuid(); const { query, params } = sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, values: [ { name: name1, @@ -1019,100 +570,32 @@ describe('sqlHelper', () => { }); describe('#getUpdateQueryAndParams()', () => { it('should not set createdAt if schema.autoCreatedAt and value is undefined', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'created_at', - propertyName: 'createdAt', - type: 'datetime', - createDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithCreatedAt.model as ModelMetadata, where: {}, values: { name, }, }); - query.should.equal(`UPDATE "${model.tableName}" SET "name"=$1 RETURNING "id","name","created_at" AS "createdAt"`); + query.should.equal(`UPDATE "${repositoriesByModelName.SimpleWithCreatedAt.model.tableName}" SET "name"=$1 RETURNING "id","name","created_at" AS "createdAt"`); params.should.deep.equal([name]); }); it('should set updatedAt if schema.autoUpdatedAt and value is undefined', () => { const beforeTime = new Date(); - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'updated_at', - propertyName: 'updatedAt', - type: 'datetime', - updateDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithUpdatedAt.model as ModelMetadata, where: {}, values: { name, }, }); - query.should.equal(`UPDATE "${model.tableName}" SET "name"=$1,"updated_at"=$2 RETURNING "id","name","updated_at" AS "updatedAt"`); + query.should.equal(`UPDATE "${repositoriesByModelName.SimpleWithUpdatedAt.model.tableName}" SET "name"=$1,"updated_at"=$2 RETURNING "id","name","updated_at" AS "updatedAt"`); params.should.have.length(2); const afterTime = new Date(); for (const [index, value] of params.entries()) { @@ -1126,44 +609,10 @@ describe('sqlHelper', () => { }); it('should not override updatedAt if schema.autoUpdatedAt and value is defined', () => { const updatedAt = faker.date.past(); - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'updated_at', - propertyName: 'updatedAt', - type: 'datetime', - updateDate: true, - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithUpdatedAt.model as ModelMetadata, where: {}, values: { name, @@ -1171,77 +620,39 @@ describe('sqlHelper', () => { }, }); - query.should.equal(`UPDATE "${model.tableName}" SET "name"=$1,"updated_at"=$2 RETURNING "id","name","updated_at" AS "updatedAt"`); + query.should.equal(`UPDATE "${repositoriesByModelName.SimpleWithUpdatedAt.model.tableName}" SET "name"=$1,"updated_at"=$2 RETURNING "id","name","updated_at" AS "updatedAt"`); params.should.deep.equal([name, updatedAt]); }); it('should ignore collection properties', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnCollectionMetadata({ - target: 'foo', - name: 'bars', - propertyName: 'bars', - collection: 'bar', - via: 'foo', - }), - new ColumnCollectionMetadata({ - target: 'foo', - name: 'bats', - propertyName: 'bats', - collection: 'bats', - through: 'foo__bats', - via: 'foo', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); + const product = new Product(); + product.id = faker.random.number(); + const category = new Category(); + category.id = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithCollections.model as ModelMetadata, where: {}, values: { name, - bars: [faker.random.uuid()], - bats: [faker.random.uuid()], + products: [product], + categories: [category], }, }); - query.should.equal(`UPDATE "${model.tableName}" SET "name"=$1 RETURNING "id","name"`); + query.should.equal(`UPDATE "${repositoriesByModelName.SimpleWithCollections.model.tableName}" SET "name"=$1 RETURNING "id","name"`); params.should.deep.equal([name]); }); it('should use primaryKey value if hydrated object is passed as a value', () => { - const store = { - id: faker.random.uuid(), - name: `store - ${faker.random.uuid()}`, - }; + const store = new Store(); + store.id = faker.random.number(); + store.name = `store - ${faker.random.uuid()}`; const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: {}, values: { name, @@ -1256,39 +667,6 @@ describe('sqlHelper', () => { }); it('should cast value to jsonb if type=json and value is an array', () => { // Please see https://github.com/brianc/node-postgres/issues/442 for details of why this is needed - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'id', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - type: 'string', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'bar', - propertyName: 'bar', - type: 'json', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const name = faker.random.uuid(); const bar = [ { @@ -1297,8 +675,8 @@ describe('sqlHelper', () => { ]; const { query, params } = sqlHelper.getUpdateQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.SimpleWithJson.model as ModelMetadata, where: {}, values: { name, @@ -1306,7 +684,7 @@ describe('sqlHelper', () => { }, }); - query.should.equal(`UPDATE "${model.tableName}" SET "name"=$1,"bar"=$2::jsonb RETURNING "id","name","bar"`); + query.should.equal(`UPDATE "${repositoriesByModelName.SimpleWithJson.model.tableName}" SET "name"=$1,"bar"=$2::jsonb RETURNING "id","name","bar"`); params.should.deep.equal([name, JSON.stringify(bar)]); }); it('should include where statement if defined', () => { @@ -1316,9 +694,9 @@ describe('sqlHelper', () => { }; const name = faker.random.uuid(); - const { query, params } = sqlHelper.getUpdateQueryAndParams({ + const { query, params } = sqlHelper.getUpdateQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: { store, }, @@ -1334,11 +712,11 @@ describe('sqlHelper', () => { }); it('should return records if returnRecords=true', () => { const productId = faker.random.uuid(); - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: { id: productId, }, @@ -1356,11 +734,11 @@ describe('sqlHelper', () => { }); it('should return specific columns for records, if returnRecords=true and returnSelect is defined', () => { const productId = faker.random.uuid(); - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { id: productId, }, @@ -1377,11 +755,11 @@ describe('sqlHelper', () => { }); it('should not return records if returnRecords=false', () => { const productId = faker.random.uuid(); - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const name = faker.random.uuid(); const { query, params } = sqlHelper.getUpdateQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { id: productId, }, @@ -1400,7 +778,7 @@ describe('sqlHelper', () => { it('should delete all records if no where statement is defined', () => { const { query, params } = sqlHelper.getDeleteQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, }); query.should.equal( @@ -1409,90 +787,23 @@ describe('sqlHelper', () => { params.should.deep.equal([]); }); it('should delete all records (non "id" primaryKey) if no where statement is defined', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'foobario', - propertyName: 'foobario', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - - const { query, params } = sqlHelper.getDeleteQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, - }); - - query.should.equal(`DELETE FROM "${model.tableName}" RETURNING "foobario","name"`); - params.should.deep.equal([]); - }); - it('should delete all records (non "id" primaryKey with id propertyName) if no where statement is defined', () => { - const model = new ModelMetadata({ - name: 'foo', - type: TestEntity, - }); - model.columns = [ - new ColumnTypeMetadata({ - target: 'foo', - name: 'foobario', - propertyName: 'id', - primary: true, - type: 'integer', - }), - new ColumnTypeMetadata({ - target: 'foo', - name: 'name', - propertyName: 'name', - required: true, - defaultsTo: 'foobar', - type: 'string', - }), - ]; - const repositories: RepositoriesByModelNameLowered = {}; - repositories[model.name.toLowerCase()] = new Repository({ - modelMetadata: model, - type: model.type, - pool: mockedPool, - repositoriesByModelNameLowered: repositories, - }); - const { query, params } = sqlHelper.getDeleteQueryAndParams({ - repositoriesByModelNameLowered: repositories, - model, + repositoriesByModelNameLowered, + model: repositoriesByModelName.NonStandardPrimaryId.model, }); - query.should.equal(`DELETE FROM "${model.tableName}" RETURNING "foobario" AS "id","name"`); + query.should.equal(`DELETE FROM "${repositoriesByModelName.NonStandardPrimaryId.model.tableName}" RETURNING "unique_id" AS "uniqueId","foo"`); params.should.deep.equal([]); }); it('should include where statement if defined', () => { const store = { - id: faker.random.uuid(), + id: faker.random.number(), name: `store - ${faker.random.uuid()}`, }; const { query, params } = sqlHelper.getDeleteQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: { store, }, @@ -1507,7 +818,7 @@ describe('sqlHelper', () => { const productId = faker.random.uuid(); const { query, params } = sqlHelper.getDeleteQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: { id: productId, }, @@ -1523,7 +834,7 @@ describe('sqlHelper', () => { const productId = faker.random.uuid(); const { query, params } = sqlHelper.getDeleteQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { id: productId, }, @@ -1538,7 +849,7 @@ describe('sqlHelper', () => { const productId = faker.random.uuid(); const { query, params } = sqlHelper.getDeleteQueryAndParams({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { id: productId, }, @@ -1552,7 +863,7 @@ describe('sqlHelper', () => { describe('#getColumnsToSelect()', () => { it('should include all columns if select is undefined (explicit)', () => { const query = sqlHelper.getColumnsToSelect({ - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, select: undefined, }); @@ -1560,14 +871,14 @@ describe('sqlHelper', () => { }); it('should include all columns if select is undefined (implicit)', () => { const query = sqlHelper.getColumnsToSelect({ - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, }); query.should.equal('"id","name","sku","alias_names" AS "aliases","store_id" AS "store","created_at" AS "createdAt"'); }); it('should include primaryKey column if select is empty', () => { const query = sqlHelper.getColumnsToSelect({ - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, select: [], }); @@ -1575,7 +886,7 @@ describe('sqlHelper', () => { }); it('should include primaryKey column if select does not include it', () => { const query = sqlHelper.getColumnsToSelect({ - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, select: ['name'], }); @@ -1586,7 +897,7 @@ describe('sqlHelper', () => { it('should return empty if where is undefined', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, }); should.not.exist(whereStatement); @@ -1596,7 +907,7 @@ describe('sqlHelper', () => { ((): void => { sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - testing a value not allowed by type definition @@ -1606,10 +917,10 @@ describe('sqlHelper', () => { }).should.throw(Error, `Attempting to query with an undefined value. store on ${repositoriesByModelNameLowered.product.model.name}`); }); it('should use column name if defined', () => { - const storeId = faker.random.uuid(); + const storeId = faker.random.number(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { store: storeId, }, @@ -1623,7 +934,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name, }, @@ -1637,7 +948,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { startsWith: name, @@ -1654,7 +965,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { startsWith: [name1, name2], @@ -1670,7 +981,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { endsWith: name, @@ -1687,7 +998,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { endsWith: [name1, name2], @@ -1703,7 +1014,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { contains: name, @@ -1720,7 +1031,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { contains: [name1, name2], @@ -1736,7 +1047,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { like: name, @@ -1752,7 +1063,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': { @@ -1769,7 +1080,7 @@ describe('sqlHelper', () => { it('should handle like with an empty value', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { like: '', @@ -1784,7 +1095,7 @@ describe('sqlHelper', () => { it('should handle not like with an empty value', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': { @@ -1802,7 +1113,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { like: [name], @@ -1818,7 +1129,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': { @@ -1837,7 +1148,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { like: [name1, name2], @@ -1853,7 +1164,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { like: [null, '', name], @@ -1870,7 +1181,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': { @@ -1888,7 +1199,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': { @@ -1905,7 +1216,7 @@ describe('sqlHelper', () => { it('should handle like with an empty array', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { like: [], @@ -1920,7 +1231,7 @@ describe('sqlHelper', () => { it('should handle not like with an empty array', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': { @@ -1938,7 +1249,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { aliases: { like: [name], @@ -1954,7 +1265,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { aliases: { '!': { @@ -1972,7 +1283,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { aliases: { like: name, @@ -1988,7 +1299,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { aliases: { '!': { @@ -2007,7 +1318,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { aliases: { like: [name1, name2], @@ -2024,7 +1335,7 @@ describe('sqlHelper', () => { const name2 = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { aliases: { '!': { @@ -2042,7 +1353,7 @@ describe('sqlHelper', () => { const now = new Date(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.productwithcreatedat.model, + model: repositoriesByModelNameLowered.productwithcreatedat.model as ModelMetadata, where: { createdAt: { '>': now, @@ -2059,7 +1370,7 @@ describe('sqlHelper', () => { const store = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { or: [ { @@ -2086,7 +1397,7 @@ describe('sqlHelper', () => { const sku = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { id, or: [ @@ -2112,7 +1423,7 @@ describe('sqlHelper', () => { const name = [faker.random.uuid(), faker.random.uuid()]; const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name, }, @@ -2127,7 +1438,7 @@ describe('sqlHelper', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { intColumn: values, }, @@ -2142,7 +1453,7 @@ describe('sqlHelper', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { floatColumn: values, }, @@ -2156,7 +1467,7 @@ describe('sqlHelper', () => { it('should handle empty array value with array type column', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: [], }, @@ -2169,7 +1480,7 @@ describe('sqlHelper', () => { it('should handle comparing array type as an array of null or empty', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: [null, []], }, @@ -2183,7 +1494,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: value, }, @@ -2197,7 +1508,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: [value], }, @@ -2211,7 +1522,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: { '!': value, @@ -2227,7 +1538,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: { '!': [value], @@ -2243,7 +1554,7 @@ describe('sqlHelper', () => { const values = [faker.random.uuid(), faker.random.uuid()]; const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: values, }, @@ -2257,7 +1568,7 @@ describe('sqlHelper', () => { const values = [faker.random.uuid(), faker.random.uuid()]; const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { arrayColumn: { '!': values, @@ -2274,7 +1585,7 @@ describe('sqlHelper', () => { it('should handle empty array value with array type column', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: [], }, @@ -2287,7 +1598,7 @@ describe('sqlHelper', () => { it('should handle comparing array type as an array of null or empty', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: [null, []], }, @@ -2301,7 +1612,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: value, }, @@ -2315,7 +1626,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: [value], }, @@ -2329,7 +1640,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: { '!': value, @@ -2345,7 +1656,7 @@ describe('sqlHelper', () => { const value = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: { '!': [value], @@ -2361,7 +1672,7 @@ describe('sqlHelper', () => { const values = [faker.random.uuid(), faker.random.uuid()]; const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: values, }, @@ -2375,7 +1686,7 @@ describe('sqlHelper', () => { const values = [faker.random.uuid(), faker.random.uuid()]; const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, where: { stringArrayColumn: { '!': values, @@ -2391,7 +1702,7 @@ describe('sqlHelper', () => { it('should treat empty array value as "false"', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: [], }, @@ -2404,7 +1715,7 @@ describe('sqlHelper', () => { it('should treat negated empty array value as "true"', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': [], @@ -2420,7 +1731,7 @@ describe('sqlHelper', () => { const name = faker.random.uuid(); const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: [name], }, @@ -2433,7 +1744,7 @@ describe('sqlHelper', () => { it('should handle an array value with NULL explicitly', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: [null, ''], }, @@ -2447,7 +1758,7 @@ describe('sqlHelper', () => { const name = [faker.random.uuid(), faker.random.uuid()]; const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': name, @@ -2462,7 +1773,7 @@ describe('sqlHelper', () => { it('should treat negation of empty array value as "true"', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': [], @@ -2477,7 +1788,7 @@ describe('sqlHelper', () => { it('should treat negation of array value with NULL explicitly as AND statements', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { name: { '!': [null, ''], @@ -2496,7 +1807,7 @@ describe('sqlHelper', () => { const { whereStatement, params } = sqlHelper.buildWhereStatement({ repositoriesByModelNameLowered, - model: repositoriesByModelNameLowered.product.model, + model: repositoriesByModelNameLowered.product.model as ModelMetadata, where: { store, }, @@ -2510,7 +1821,7 @@ describe('sqlHelper', () => { describe('#buildOrderStatement()', () => { it('should return empty if there are no orders defined', () => { const result = sqlHelper.buildOrderStatement({ - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, sorts: [], }); @@ -2518,7 +1829,7 @@ describe('sqlHelper', () => { }); it('should handle single string order with implicit direction', () => { const result = sqlHelper.buildOrderStatement({ - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, sorts: [ { propertyName: 'name', @@ -2530,7 +1841,7 @@ describe('sqlHelper', () => { }); it('should handle single string order with implicit direction and explicit columnName', () => { const result = sqlHelper.buildOrderStatement({ - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, sorts: [ { propertyName: 'intColumn', @@ -2542,7 +1853,7 @@ describe('sqlHelper', () => { }); it('should handle single string order with explicit desc direction', () => { const result = sqlHelper.buildOrderStatement({ - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, sorts: [ { propertyName: 'name', @@ -2555,7 +1866,7 @@ describe('sqlHelper', () => { }); it('should handle single string order with explicit desc direction and explicit columnName', () => { const result = sqlHelper.buildOrderStatement({ - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, sorts: [ { propertyName: 'intColumn', @@ -2568,7 +1879,7 @@ describe('sqlHelper', () => { }); it('should handle multiple string order', () => { const result = sqlHelper.buildOrderStatement({ - model: repositoriesByModelNameLowered.kitchensink.model, + model: repositoriesByModelNameLowered.kitchensink.model as ModelMetadata, sorts: [ { propertyName: 'intColumn',