From 85c55e49721fe3388bd3a2e923181abc86100081 Mon Sep 17 00:00:00 2001 From: Jim Geurts Date: Sat, 4 Jan 2020 13:17:23 -0600 Subject: [PATCH] Release v3.3.0 * Make typescript lint rules more strict * Update npms --- .eslintrc.js | 319 ++++++++++-------- CHANGELOG.md | 4 + package.json | 26 +- src/ChainablePromiseLike.ts | 4 + src/ReadonlyRepository.ts | 75 ++-- src/Repository.ts | 20 +- src/SqlHelper.ts | 58 ++-- src/decorators/column.ts | 2 +- src/decorators/createDateColumn.ts | 2 +- src/decorators/primaryColumn.ts | 2 +- src/decorators/table.ts | 2 +- src/decorators/updateDateColumn.ts | 2 +- src/decorators/versionColumn.ts | 2 +- src/index.ts | 6 +- src/metadata/ModelMetadata.ts | 26 +- src/query/CountResult.ts | 5 +- src/query/DestroyResult.ts | 5 +- src/query/FindOneResult.ts | 5 +- src/query/FindResult.ts | 5 +- tests/.eslintrc.js | 2 + .../ProductWithCreateUpdateDateTracking.ts | 4 +- tests/readonlyRepository.tests.ts | 78 +---- tests/repository.tests.ts | 6 +- tests/sqlHelper.tests.ts | 14 +- 24 files changed, 337 insertions(+), 337 deletions(-) create mode 100644 src/ChainablePromiseLike.ts diff --git a/.eslintrc.js b/.eslintrc.js index cb1a899..1b34740 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,155 +1,161 @@ 'use strict'; -// NOTE: This eslint config is meant for js files module.exports = { - "root": true, - "plugins": [ - "jsdoc", - "mocha", - "promise", - "security", - '@typescript-eslint' + 'root': true, + 'plugins': [ + 'jsdoc', + 'mocha', + 'promise', + 'security', + 'import', + '@typescript-eslint', ], extends: [ 'eslint:recommended', 'airbnb-base', + 'plugin:import/typescript', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', ], - "env": { - "node": true, - "es6": true + 'env': { + 'node': true, + 'es6': true }, - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".ts"] + 'settings': { + 'import/resolver': { + 'node': { + 'extensions': ['.js', '.ts'] } } }, rules: { - "curly": ["error", "all"], - "callback-return": ["error", ["callback", "cb", "next", "done"]], - "class-methods-use-this": "off", - "consistent-return": "off", - "handle-callback-err": ["error", "^.*err" ], - "new-cap": "off", - "no-console": "error", - "no-else-return": "error", - "no-eq-null": "off", - "no-global-assign": "error", - "no-loop-func": "off", - "no-lone-blocks": "error", - "no-negated-condition": "error", - "no-shadow": "off", - "no-template-curly-in-string": "error", - "no-undef": "error", - "no-underscore-dangle": "off", - "no-unsafe-negation": "error", - "no-use-before-define": ["error", "nofunc"], - "no-useless-rename": "error", - "padding-line-between-statements": ["error", - { "blankLine": "always", "prev": [ - "directive", - "block", - "block-like", - "multiline-block-like", - "cjs-export", - "cjs-import", - "class", - "export", - "import", - "if" - ], "next": "*" }, - { "blankLine": "never", "prev": "directive", "next": "directive" }, - { "blankLine": "any", "prev": "*", "next": ["if", "for", "cjs-import", "import"] }, - { "blankLine": "any", "prev": ["export", "import"], "next": ["export", "import"] }, - { "blankLine": "always", "prev": "*", "next": ["try", "function", "switch"] }, - { "blankLine": "always", "prev": "if", "next": "if" }, - { "blankLine": "never", "prev": ["return", "throw"], "next": "*" } + 'curly': ['error', 'all'], + 'callback-return': ['error', ['callback', 'cb', 'next', 'done']], + 'class-methods-use-this': 'off', + 'consistent-return': 'off', + 'handle-callback-err': ['error', '^.*err' ], + 'new-cap': 'off', + 'no-console': 'error', + 'no-else-return': 'error', + 'no-eq-null': 'off', + 'no-global-assign': 'error', + 'no-loop-func': 'off', + 'no-lone-blocks': 'error', + 'no-negated-condition': 'error', + 'no-shadow': 'off', + 'no-template-curly-in-string': 'error', + 'no-undef': 'error', + 'no-underscore-dangle': 'off', + 'no-unsafe-negation': 'error', + 'no-use-before-define': ['error', 'nofunc'], + 'no-useless-rename': 'error', + 'padding-line-between-statements': ['error', + { 'blankLine': 'always', 'prev': [ + 'directive', + 'block', + 'block-like', + 'multiline-block-like', + 'cjs-export', + 'cjs-import', + 'class', + 'export', + 'import', + 'if' + ], 'next': '*' }, + { 'blankLine': 'never', 'prev': 'directive', 'next': 'directive' }, + { 'blankLine': 'any', 'prev': '*', 'next': ['if', 'for', 'cjs-import', 'import'] }, + { 'blankLine': 'any', 'prev': ['export', 'import'], 'next': ['export', 'import'] }, + { 'blankLine': 'always', 'prev': '*', 'next': ['try', 'function', 'switch'] }, + { 'blankLine': 'always', 'prev': 'if', 'next': 'if' }, + { 'blankLine': 'never', 'prev': ['return', 'throw'], 'next': '*' } ], - "strict": ["error", "safe"], - "no-empty": "error", - "no-empty-function": "error", - "valid-jsdoc": "off", - "yoda": "error", + 'strict': ['error', 'safe'], + 'no-new': 'off', + 'no-empty': 'error', + 'no-empty-function': 'error', + 'valid-jsdoc': 'off', + 'yoda': 'error', - "import/no-unresolved": "off", + 'import/extensions': ['error', 'never'], + 'import/no-unresolved': 'off', - "jsdoc/check-alignment": "error", - "jsdoc/check-indentation": "off", - "jsdoc/check-param-names": "off", - "jsdoc/check-tag-names": "error", - "jsdoc/check-types": "error", - "jsdoc/newline-after-description": "off", - "jsdoc/no-undefined-types": "off", - "jsdoc/require-description": "off", - "jsdoc/require-description-complete-sentence": "off", - "jsdoc/require-example": "off", - "jsdoc/require-hyphen-before-param-description": "error", - "jsdoc/require-param": "error", - "jsdoc/require-param-description": "off", - "jsdoc/require-param-name": "error", - "jsdoc/require-param-type": "error", - "jsdoc/require-returns-description": "off", - "jsdoc/require-returns-type": "error", - "jsdoc/valid-types": "error", + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-indentation': 'off', + 'jsdoc/check-param-names': 'off', + 'jsdoc/check-tag-names': 'error', + 'jsdoc/check-types': 'error', + 'jsdoc/newline-after-description': 'off', + 'jsdoc/no-undefined-types': 'off', + 'jsdoc/require-description': 'off', + 'jsdoc/require-description-complete-sentence': 'off', + 'jsdoc/require-example': 'off', + 'jsdoc/require-hyphen-before-param-description': 'error', + 'jsdoc/require-param': 'error', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-param-name': 'error', + 'jsdoc/require-param-type': 'error', + 'jsdoc/require-returns-description': 'off', + 'jsdoc/require-returns-type': 'error', + 'jsdoc/valid-types': 'error', - "promise/always-return": "error", - "promise/always-catch": "off", - "promise/catch-or-return": ["error", {"allowThen": true }], - "promise/no-native": "off", - "promise/param-names": "error", + 'promise/always-return': 'error', + 'promise/always-catch': 'off', + 'promise/catch-or-return': ['error', {'allowThen': true }], + 'promise/no-native': 'off', + 'promise/param-names': 'error', - "security/detect-buffer-noassert": "error", - "security/detect-child-process": "error", - "security/detect-disable-mustache-escape": "error", - "security/detect-eval-with-expression": "error", - "security/detect-new-buffer": "error", - "security/detect-no-csrf-before-method-override": "error", - "security/detect-non-literal-fs-filename": "error", - "security/detect-non-literal-regexp": "error", - "security/detect-non-literal-require": "off", - "security/detect-object-injection": "off", - "security/detect-possible-timing-attacks": "error", - "security/detect-pseudoRandomBytes": "error", - "security/detect-unsafe-regex": "error", + 'security/detect-buffer-noassert': 'error', + 'security/detect-child-process': 'error', + 'security/detect-disable-mustache-escape': 'error', + 'security/detect-eval-with-expression': 'error', + 'security/detect-new-buffer': 'error', + 'security/detect-no-csrf-before-method-override': 'error', + 'security/detect-non-literal-fs-filename': 'error', + 'security/detect-non-literal-regexp': 'error', + 'security/detect-non-literal-require': 'off', + 'security/detect-object-injection': 'off', + 'security/detect-possible-timing-attacks': 'error', + 'security/detect-pseudoRandomBytes': 'error', + 'security/detect-unsafe-regex': 'error', // Override airbnb - "eqeqeq": ["error", "smart"], - "func-names": "error", - "id-length": ["error", {"exceptions": ["_", "$", "e", "i", "j", "k", "q", "x", "y"]}], - "no-param-reassign": "off", // Work toward enforcing this rule - "radix": "off", - "spaced-comment": "off", - "max-len": "off", - "no-continue": "off", - "no-plusplus": "off", - "no-prototype-builtins": "off", - "no-restricted-syntax": [ - "error", - "DebuggerStatement", - "LabeledStatement", - "WithStatement" + 'eqeqeq': ['error', 'smart'], + 'func-names': 'error', + 'id-length': ['error', {'exceptions': ['_', '$', 'e', 'i', 'j', 'k', 'q', 'x', 'y']}], + 'no-param-reassign': 'off', // Work toward enforcing this rule + 'radix': 'off', + 'spaced-comment': 'off', + 'max-len': 'off', + 'no-continue': 'off', + 'no-plusplus': 'off', + 'no-prototype-builtins': 'off', + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement' ], - "no-restricted-properties": ["error", { - "object": "arguments", - "property": "callee", - "message": "arguments.callee is deprecated" + 'no-restricted-properties': ['error', { + 'object': 'arguments', + 'property': 'callee', + 'message': 'arguments.callee is deprecated' }, { - "property": "__defineGetter__", - "message": "Please use Object.defineProperty instead." + 'property': '__defineGetter__', + 'message': 'Please use Object.defineProperty instead.' }, { - "property": "__defineSetter__", - "message": "Please use Object.defineProperty instead." + 'property': '__defineSetter__', + 'message': 'Please use Object.defineProperty instead.' }], - "no-useless-escape": "off", - "object-shorthand": ["error", "always", { - "ignoreConstructors": false, - "avoidQuotes": true, - "avoidExplicitReturnArrows": true + 'no-useless-escape': 'off', + 'object-shorthand': ['error', 'always', { + 'ignoreConstructors': false, + 'avoidQuotes': true, + 'avoidExplicitReturnArrows': true }], - // "prefer-arrow-callback": ["error", { "allowNamedFunctions": true }], - "prefer-spread": "error", - "prefer-destructuring": "off" + // 'prefer-arrow-callback': ['error', { 'allowNamedFunctions': true }], + 'prefer-spread': 'error', + 'prefer-destructuring': 'off' }, overrides: [{ files: [ @@ -180,37 +186,80 @@ module.exports = { rules: { 'class-methods-use-this': 'off', 'indent': 'off', - "max-len": "off", + 'max-len': 'off', 'no-dupe-class-members': 'off', + 'no-extra-semi': 'off', + 'no-new': 'off', + 'no-param-reassign': 'off', 'no-underscore-dangle': 'off', 'no-useless-constructor': 'off', - "no-restricted-syntax": [ - "error", - "DebuggerStatement", - "LabeledStatement", - "WithStatement" + 'no-unused-expressions': 'error', + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement' ], 'import/prefer-default-export': 'off', + 'import/no-cycle': 'off', 'import/no-extraneous-dependencies': 'off', + 'import/extensions': ['error', 'never'], + '@typescript-eslint/array-type': ['error', { default: 'array' }], '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/adjacent-overload-signatures': 'error', + '@typescript-eslint/class-name-casing': 'error', + '@typescript-eslint/consistent-type-assertions': 'error', + '@typescript-eslint/consistent-type-definitions': 'error', '@typescript-eslint/no-extraneous-class': 'error', - '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-function-return-type': 'error', '@typescript-eslint/explicit-member-accessibility': ["error"], + '@typescript-eslint/generic-type-naming': 'error', '@typescript-eslint/interface-name-prefix': ['error', 'never'], + '@typescript-eslint/member-ordering': ['error', { + default: [ + // Index signature + 'signature', + // Fields + 'private-field', + 'public-field', + 'protected-field', + // Constructors + 'public-constructor', + 'protected-constructor', + 'private-constructor', + // Methods + 'public-method', + 'protected-method', + 'private-method', + ], + }], + '@typescript-eslint/no-array-constructor': 'error', + '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-extra-semi': 'error', + '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-for-in-array': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-parameter-properties': ['error', { allows: ['readonly'] }], '@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-this-alias': 'error', + '@typescript-eslint/no-throw-literal': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unused-expressions': 'error', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-includes': 'error', + '@typescript-eslint/prefer-regexp-exec': 'warn', '@typescript-eslint/prefer-string-starts-ends-with': 'error', '@typescript-eslint/promise-function-async': 'off', + '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': 'error', + '@typescript-eslint/unbound-method': 'error', + '@typescript-eslint/unified-signatures': 'error', }, }] }; diff --git a/CHANGELOG.md b/CHANGELOG.md index ab00a61..3c2982c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 3.3.0 + * Make typescript lint rules more strict + * Update npms + ### 3.2.0 * Fix CreateUpdateDelete type to be strict about either returnRecords=false or defined returnSelect * Update npms diff --git a/package.json b/package.json index 4701728..d765fdc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigal", - "version": "3.2.0", + "version": "3.3.0", "description": "A fast and lightweight orm for postgres and node.js, written in typescript.", "main": "index.js", "types": "index.d.ts", @@ -45,22 +45,22 @@ "dependencies": { "@types/lodash": "~4.14.144", "@types/node": "~10.14.22", - "@types/pg": "~7.11.2", + "@types/pg": "~7.14.1", "lodash": "~4.17.15", - "pg": "7.14.0", - "postgres-pool": "~2.0.2" + "pg": "~7.17.0", + "postgres-pool": "~2.0.3" }, "devDependencies": { - "@types/chai": "4.2.5", - "@types/faker": "4.1.7", + "@types/chai": "4.2.7", + "@types/faker": "4.1.8", "@types/mocha": "5.2.7", - "@typescript-eslint/eslint-plugin": "2.9.0", - "@typescript-eslint/parser": "2.9.0", + "@typescript-eslint/eslint-plugin": "2.14.0", + "@typescript-eslint/parser": "2.14.0", "chai": "4.2.0", - "eslint": "6.7.1", + "eslint": "6.8.0", "eslint-config-airbnb-base": "14.0.0", - "eslint-plugin-import": "2.18.2", - "eslint-plugin-jsdoc": "18.1.5", + "eslint-plugin-import": "2.19.1", + "eslint-plugin-jsdoc": "20.0.0", "eslint-plugin-mocha": "6.2.2", "eslint-plugin-promise": "4.2.1", "eslint-plugin-security": "1.4.0", @@ -70,7 +70,7 @@ "mocha": "6.2.2", "strict-event-emitter-types": "2.0.0", "ts-mockito": "2.5.0", - "ts-node": "8.5.2", - "typescript": "3.7.2" + "ts-node": "8.5.4", + "typescript": "3.7.4" } } diff --git a/src/ChainablePromiseLike.ts b/src/ChainablePromiseLike.ts new file mode 100644 index 0000000..cb1c4f0 --- /dev/null +++ b/src/ChainablePromiseLike.ts @@ -0,0 +1,4 @@ +export interface ChainablePromiseLike { + then(resolve: (value: TResult) => TResult | PromiseLike, reject: (error: Error) => TRejectResult | PromiseLike): Promise; + then(resolve: (value: TResult) => TResult | PromiseLike, reject: (error: Error) => void | PromiseLike): Promise; +} diff --git a/src/ReadonlyRepository.ts b/src/ReadonlyRepository.ts index ebf4d08..90ad0b0 100644 --- a/src/ReadonlyRepository.ts +++ b/src/ReadonlyRepository.ts @@ -34,6 +34,8 @@ export interface RepositoryOptions { } export class ReadonlyRepository { + private readonly _modelMetadata: ModelMetadata; + protected _type: EntityStatic; protected _pool: Pool; @@ -46,8 +48,6 @@ export class ReadonlyRepository { protected _intProperties: string[] = []; - private readonly _modelMetadata: ModelMetadata; - public constructor({ modelMetadata, type, @@ -120,7 +120,7 @@ export class ReadonlyRepository { } const populates: Populate[] = []; - const sorts: Array = []; + const sorts: (string | object)[] = []; if (_.isArray(sort)) { sorts.push(...sort); } else if (sort) { @@ -135,7 +135,7 @@ export class ReadonlyRepository { * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery) { + where(value: WhereQuery): FindOneResult { where = value; return this; @@ -155,7 +155,7 @@ export class ReadonlyRepository { sort: populateSort, skip: populateSkip, limit: populateLimit, - }: PopulateArgs = {}) { + }: PopulateArgs = {}): FindOneResult { populates.push({ propertyName, where: populateWhere, @@ -171,12 +171,12 @@ export class ReadonlyRepository { * Sorts the query * @param {string|object} value */ - sort(value: string | object) { + sort(value: string | object): FindOneResult { sorts.push(value); return this; }, - async then(resolve: (result: T | null) => (T | Promise | null), reject: (err: Error) => (void | Promise) | undefined): Promise { + async then(resolve: (result: T | null) => (T | Promise | null), reject: (err: Error) => void): Promise { try { if (_.isString(where)) { throw new Error('The query cannot be a string, it must be an object'); @@ -219,17 +219,19 @@ export class ReadonlyRepository { } const populateWhere = _.merge({ + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) [populateRepository.model.primaryKeyColumn.propertyName]: result[populate.propertyName], }, populate.where); - populateQueries.push(async function populateModel() { + populateQueries.push(async function populateModel(): Promise { const populateResult = await populateRepository.findOne({ select: populate.select, where: populateWhere, sort: populate.sort, }); + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) result[populate.propertyName] = populateResult; }()); @@ -249,6 +251,7 @@ export class ReadonlyRepository { throw new Error(`Unable to populate ${column.target}#${column.propertyName}. There is no primary key defined in ${modelInstance.model.name}`); } + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - Ignoring result does not have index signature for known field (primaryKeyColumn.propertyName) const id = result[primaryKeyColumn.propertyName]; if (_.isNil(id)) { @@ -274,7 +277,7 @@ export class ReadonlyRepository { throw new Error(`Unable to find property on related model for multi-map collection: ${collectionColumn.through}. From ${column.target}#${populate.propertyName}`); } - populateQueries.push(async function populateMultiMulti() { + populateQueries.push(async function populateMultiMulti(): Promise { if (relatedModelColumn) { const mapRecords = await throughRepository.find({ select: [relatedModelColumn.via], @@ -296,6 +299,7 @@ export class ReadonlyRepository { limit: populate.limit, }); + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) result[populate.propertyName] = populateResults; } @@ -305,7 +309,7 @@ export class ReadonlyRepository { [collectionColumn.via]: id, }, populate.where); - populateQueries.push(async function populateCollection() { + populateQueries.push(async function populateCollection(): Promise { const populateResults = await populateRepository.find({ select: populate.select, where: populateWhere, @@ -314,6 +318,7 @@ export class ReadonlyRepository { limit: populate.limit, }); + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore - Ignoring result does not have index signature for known field (populate.propertyName) result[populate.propertyName] = populateResults; }()); @@ -325,13 +330,15 @@ export class ReadonlyRepository { await Promise.all(populateQueries); } - return resolve(result as T); + return resolve(result); } return resolve(null); } catch (ex) { ex.stack += stack; - return reject(ex); + reject(ex); + + return null; } }, }; @@ -390,7 +397,7 @@ export class ReadonlyRepository { } } - const sorts: Array = []; + const sorts: (string | object)[] = []; if (_.isArray(sort)) { sorts.push(...sort); } else if (sort) { @@ -405,7 +412,7 @@ export class ReadonlyRepository { * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery) { + where(value: WhereQuery): FindResult { where = value; return this; @@ -414,7 +421,7 @@ export class ReadonlyRepository { * Sorts the query * @param {string|object} value */ - sort(value: string | object) { + sort(value: string | object): FindResult { sorts.push(value); return this; @@ -423,7 +430,7 @@ export class ReadonlyRepository { * Limits results returned by the query * @param {number} value */ - limit(value: number) { + limit(value: number): FindResult { limit = value; return this; @@ -432,7 +439,7 @@ export class ReadonlyRepository { * Skips records returned by the query * @param {number} value */ - skip(value: number) { + skip(value: number): FindResult { skip = value; return this; @@ -445,17 +452,17 @@ export class ReadonlyRepository { paginate({ page = 1, limit: paginateLimit = 10, - }: PaginateOptions) { + }: PaginateOptions): FindResult { const safePage = Math.max(page, 1); - this.skip((safePage * paginateLimit) - paginateLimit); - this.limit(paginateLimit); - - return this; + return this + .skip((safePage * paginateLimit) - paginateLimit) + .limit(paginateLimit); }, - async then(resolve: (result: T[]) => void, reject: (err: Error) => void) { + async then(resolve: (result: T[]) => T[], reject: (err: Error) => void): Promise { try { if (_.isString(where)) { reject(new Error('The query cannot be a string, it must be an object')); + return []; } const { @@ -472,10 +479,11 @@ export class ReadonlyRepository { }); const results = await modelInstance._readonlyPool.query(query, params); - resolve(modelInstance._buildInstances(results.rows)); + return resolve(modelInstance._buildInstances(results.rows)); } catch (ex) { ex.stack += stack; reject(ex); + return []; } }, }; @@ -499,13 +507,13 @@ export class ReadonlyRepository { * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery) { + where(value: WhereQuery): CountResult | number { // eslint-disable-next-line no-param-reassign where = value; return this; }, - async then(resolve: (result: number) => void, reject: (err: Error) => void) { + async then(resolve: (result: number) => number, reject: (err: Error) => void): Promise { try { const { query, @@ -528,7 +536,8 @@ export class ReadonlyRepository { return resolve(originalValue); } catch (ex) { ex.stack += stack; - return reject(ex); + reject(ex); + return 0; } }, }; @@ -539,18 +548,18 @@ export class ReadonlyRepository { return row; } - const instance = new this._type() as T; + const instance = new this._type(); Object.assign(instance, row); // NOTE: Number fields may be strings coming from the db. In those cases, try to convert the value to Number for (const name of this._floatProperties) { - // @ts-ignore const originalValue = row[name] as string | number | undefined | null; if (!_.isNil(originalValue) && typeof originalValue === 'string') { try { const value = Number(originalValue); if (_.isFinite(value) && value.toString() === originalValue) { - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore - string cannot be used to index type T instance[name] = value; } } catch (ex) { @@ -560,7 +569,6 @@ export class ReadonlyRepository { } for (const name of this._intProperties) { - // @ts-ignore const originalValue = row[name] as string | number | undefined | null; if (!_.isNil(originalValue) && typeof originalValue === 'string') { try { @@ -568,7 +576,8 @@ export class ReadonlyRepository { if (_.isFinite(value) && value.toString() === originalValue) { const valueAsInt = _.toInteger(value); if (Number.isSafeInteger(valueAsInt)) { - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore - string cannot be used to index type T instance[name] = valueAsInt; } } @@ -581,7 +590,7 @@ export class ReadonlyRepository { return instance; } - protected _buildInstances(rows: Array>): T[] { + protected _buildInstances(rows: Partial[]): T[] { if (_.isNil(rows)) { return rows; } diff --git a/src/Repository.ts b/src/Repository.ts index 17ee242..7c66cdc 100644 --- a/src/Repository.ts +++ b/src/Repository.ts @@ -31,7 +31,7 @@ export class Repository extends ReadonlyRepository { * @param {{returnRecords: false}} options * @returns {boolean} */ - public async create(values: Partial | Array>, options: DoNotReturnRecords): Promise; + public async create(values: Partial | Partial[], options: DoNotReturnRecords): Promise; /** * Creates a objects using the specified values @@ -41,7 +41,7 @@ export class Repository extends ReadonlyRepository { * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. * @returns {boolean} */ - public async create(values: Array>, options?: CreateUpdateDeleteOptions): Promise; + public async create(values: Partial[], options?: CreateUpdateDeleteOptions): Promise; /** * Creates an object using the specified values @@ -51,7 +51,7 @@ export class Repository extends ReadonlyRepository { * @param {string[]} [options.returnSelect] - Array of model property names to return from the query. * @returns {object} Return value from the db */ - public async create(values: Partial | Array>, options?: CreateUpdateDeleteOptions): Promise { + public async create(values: Partial | Partial[], options?: CreateUpdateDeleteOptions): Promise { if (this.model.readonly) { throw new Error(`${this.model.name} is readonly.`); } @@ -63,10 +63,10 @@ export class Repository extends ReadonlyRepository { if (this._type.beforeCreate) { if (Array.isArray(values)) { // eslint-disable-next-line no-param-reassign - values = await Promise.all(values.map(this._type.beforeCreate)) as Array>; + values = await Promise.all(values.map(this._type.beforeCreate)); } else { // eslint-disable-next-line no-param-reassign - values = await this._type.beforeCreate(values) as Partial; + values = await this._type.beforeCreate(values); } } @@ -147,7 +147,7 @@ export class Repository extends ReadonlyRepository { if (this._type.beforeUpdate) { // eslint-disable-next-line no-param-reassign - values = await this._type.beforeUpdate(values) as Partial; + values = await this._type.beforeUpdate(values); } let returnRecords = true; @@ -233,15 +233,16 @@ export class Repository extends ReadonlyRepository { * Filters the query * @param {object} value - Object representing the where query */ - where(value: WhereQuery) { + where(value: WhereQuery): DestroyResult { // eslint-disable-next-line no-param-reassign where = value; return this; }, - async then(resolve: (result: T[] | boolean) => void, reject: (err: Error) => void) { + async then(resolve: (result: T[] | boolean) => T[] | boolean, reject: (err: Error) => void): Promise { if (_.isString(where)) { reject(new Error('The query cannot be a string, it must be an object')); + return false; } try { @@ -265,7 +266,8 @@ export class Repository extends ReadonlyRepository { return resolve(true); } catch (ex) { ex.stack += stack; - return reject(ex); + reject(ex); + return false; } }, }; diff --git a/src/SqlHelper.ts b/src/SqlHelper.ts index ac1cf86..ae6bed6 100644 --- a/src/SqlHelper.ts +++ b/src/SqlHelper.ts @@ -46,7 +46,7 @@ export function getSelectQueryAndParams({ model: ModelMetadata; select?: string[]; where?: WhereQuery; - sorts: Array; + sorts: (string | object)[]; skip: number; limit: number; }): QueryAndParams { @@ -168,9 +168,9 @@ export function getInsertQueryAndParams({ }: { repositoriesByModelNameLowered: RepositoriesByModelNameLowered; model: ModelMetadata; - values: Partial | Array>; + values: Partial | Partial[]; returnRecords?: boolean; - returnSelect?: Array>; + returnSelect?: Extract[]; }): QueryAndParams { const entitiesToInsert = _.isArray(values) ? values : [values]; const columnsToInsert = []; @@ -315,7 +315,7 @@ export function getUpdateQueryAndParams({ where: WhereQuery; values: Partial; returnRecords?: boolean; - returnSelect?: Array>; + returnSelect?: Extract[]; }): QueryAndParams { for (const column of model.updateDateColumns) { if (_.isUndefined(values[column.propertyName])) { @@ -435,7 +435,7 @@ export function getDeleteQueryAndParams({ model: ModelMetadata; where?: WhereQuery; returnRecords?: boolean; - returnSelect?: Array>; + returnSelect?: Extract[]; }): QueryAndParams { let query = `DELETE FROM "${model.tableName}"`; @@ -478,7 +478,7 @@ export function _getColumnsToSelect({ select, }: { model: ModelMetadata; - select?: Array>; + select?: Extract[]; }): string { if (select) { const { primaryKeyColumn } = model; @@ -576,17 +576,17 @@ export function _buildOrderStatement({ sorts, }: { model: ModelMetadata; - sorts: Array; + sorts: (string | object)[]; }): string { if (_.isNil(sorts) || !_.some(sorts)) { return ''; } let orderStatement = 'ORDER BY '; - const orderProperties: Array<{ + const orderProperties: { propertyName: string; order: number | string; - }> = []; + }[] = []; for (const sortStatement of sorts) { if (_.isString(sortStatement)) { for (const sort of sortStatement.split(',')) { @@ -787,7 +787,8 @@ function _buildWhere({ case 'like': return _buildLikeOperatorStatement({ model, - propertyName, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + propertyName: propertyName!, isNegated, value, params, @@ -982,7 +983,7 @@ function _buildOrOperatorStatement({ value: string[] | number[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any params: any[]; -}) { +}): string { const orClauses = []; for (const constraint of value) { const orClause = _buildWhere({ @@ -1007,21 +1008,23 @@ function _buildOrOperatorStatement({ return `(${orClauses.join(' OR ')})`; } -function _buildLikeOperatorStatement({ - model, - propertyName, - isNegated, - value, - params, - }: { +interface ComparisonOperatorStatementParams { model: ModelMetadata; - propertyName?: string; + propertyName: string; comparer?: Comparer | string; isNegated: boolean; value?: WhereClauseValue; // eslint-disable-next-line @typescript-eslint/no-explicit-any params: any[]; -}) { +} + +function _buildLikeOperatorStatement({ + model, + propertyName, + isNegated, + value, + params, + }: ComparisonOperatorStatementParams): string { if (_.isArray(value)) { if (!value.length) { if (isNegated) { @@ -1037,8 +1040,7 @@ function _buildLikeOperatorStatement({ // NOTE: This is doing a case-insensitive pattern match params.push(lowerValues); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const column = model.columnsByPropertyName[propertyName!]; + const column = model.columnsByPropertyName[propertyName]; if (!column) { throw new Error(`Unable to find property ${propertyName} on model ${model.name}`); } @@ -1057,7 +1059,7 @@ function _buildLikeOperatorStatement({ if (_.isString(value)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const column = model.columnsByPropertyName[propertyName!]; + const column = model.columnsByPropertyName[propertyName]; if (!column) { throw new Error(`Unable to find property ${propertyName} on model ${model.name}`); } @@ -1087,15 +1089,7 @@ function _buildComparisonOperatorStatement({ isNegated, value, params = [], - }: { - model: ModelMetadata; - propertyName: string; - comparer?: Comparer | string; - isNegated: boolean; - value: string | number | Date | boolean | WhereQuery | null | Entity; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: any[]; -}) { + }: ComparisonOperatorStatementParams): string { const column = model.columnsByPropertyName[propertyName]; if (!column) { throw new Error(`Unable to find property ${propertyName} on model ${model.name}`); diff --git a/src/decorators/column.ts b/src/decorators/column.ts index 38a4b3b..a338c75 100644 --- a/src/decorators/column.ts +++ b/src/decorators/column.ts @@ -15,7 +15,7 @@ type ReturnFunctionType = (object: object, propertyName: string) => void; export function column(options?: ColumnOptions): ReturnFunctionType; export function column(dbColumnName: string, options?: ColumnOptions): ReturnFunctionType; export function column(dbColumnNameOrOptions?: string | ColumnOptions, options?: ColumnOptions): ReturnFunctionType { - return function columnDecorator(object: object, propertyName: string) { + return function columnDecorator(object: object, propertyName: string): void { if (!dbColumnNameOrOptions) { // eslint-disable-next-line no-param-reassign dbColumnNameOrOptions = _.snakeCase(propertyName); diff --git a/src/decorators/createDateColumn.ts b/src/decorators/createDateColumn.ts index ce79ccb..d6c008c 100644 --- a/src/decorators/createDateColumn.ts +++ b/src/decorators/createDateColumn.ts @@ -11,7 +11,7 @@ type ReturnFunctionType = (object: object, propertyName: string) => void; export function createDateColumn(options?: ColumnTypeOptions): ReturnFunctionType; export function createDateColumn(dbColumnName: string, options?: ColumnTypeOptions): ReturnFunctionType; export function createDateColumn(dbColumnNameOrOptions?: string | ColumnTypeOptions, options?: ColumnTypeOptions): ReturnFunctionType { - return function createDateColumnDecorator(object: object, propertyName: string) { + return function createDateColumnDecorator(object: object, propertyName: string): void { const metadataStorage = getMetadataStorage(); let dbColumnName: string | undefined; if (typeof dbColumnNameOrOptions === 'string') { diff --git a/src/decorators/primaryColumn.ts b/src/decorators/primaryColumn.ts index ab00ad4..e9ad015 100644 --- a/src/decorators/primaryColumn.ts +++ b/src/decorators/primaryColumn.ts @@ -15,7 +15,7 @@ type ReturnFunctionType = (object: object, propertyName: string) => void; export function primaryColumn(options?: ColumnOptions): ReturnFunctionType; export function primaryColumn(dbColumnName: string, options?: ColumnOptions): ReturnFunctionType; export function primaryColumn(dbColumnNameOrOptions?: string | ColumnOptions, options?: ColumnOptions): ReturnFunctionType { - return function primaryColumnDecorator(object: object, propertyName: string) { + return function primaryColumnDecorator(object: object, propertyName: string): void { let dbColumnName: string | undefined; if (typeof dbColumnNameOrOptions === 'string') { dbColumnName = dbColumnNameOrOptions; diff --git a/src/decorators/table.ts b/src/decorators/table.ts index 32f39ba..abcffde 100644 --- a/src/decorators/table.ts +++ b/src/decorators/table.ts @@ -11,7 +11,7 @@ type ReturnFunctionType = (object: EntityStatic) = export function table(options?: TableOptions): ReturnFunctionType; export function table(dbName: string, options: TableOptions): ReturnFunctionType; export function table(dbNameOrTableOptions?: string | TableOptions, options?: TableOptions): ReturnFunctionType { - return function tableDecorator(classObject: EntityStatic) { + return function tableDecorator(classObject: EntityStatic): void { const className = classObject.name; let dbTableName: string | undefined; diff --git a/src/decorators/updateDateColumn.ts b/src/decorators/updateDateColumn.ts index 56a3c45..2bced5e 100644 --- a/src/decorators/updateDateColumn.ts +++ b/src/decorators/updateDateColumn.ts @@ -11,7 +11,7 @@ type ReturnFunctionType = (object: object, propertyName: string) => void; export function updateDateColumn(options?: ColumnTypeOptions): ReturnFunctionType; export function updateDateColumn(dbColumnName: string, options?: ColumnTypeOptions): ReturnFunctionType; export function updateDateColumn(dbColumnNameOrOptions?: string | ColumnTypeOptions, options?: ColumnTypeOptions): ReturnFunctionType { - return function updateDateColumnDecorator(object: object, propertyName: string) { + return function updateDateColumnDecorator(object: object, propertyName: string): void { let dbColumnName: string | undefined; if (typeof dbColumnNameOrOptions === 'string') { dbColumnName = dbColumnNameOrOptions; diff --git a/src/decorators/versionColumn.ts b/src/decorators/versionColumn.ts index 8be6131..3ab3c2e 100644 --- a/src/decorators/versionColumn.ts +++ b/src/decorators/versionColumn.ts @@ -11,7 +11,7 @@ type ReturnFunctionType = (object: object, propertyName: string) => void; export function versionColumn(options?: ColumnTypeOptions): ReturnFunctionType; export function versionColumn(dbColumnName: string, options?: ColumnTypeOptions): ReturnFunctionType; export function versionColumn(dbColumnNameOrOptions?: string | ColumnTypeOptions, options?: ColumnTypeOptions): ReturnFunctionType { - return function versionColumnDecorator(object: object, propertyName: string) { + return function versionColumnDecorator(object: object, propertyName: string): void { let dbColumnName: string | undefined; if (typeof dbColumnNameOrOptions === 'string') { dbColumnName = dbColumnNameOrOptions; diff --git a/src/index.ts b/src/index.ts index 79e79a4..974075f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,7 +32,7 @@ export interface InitializeOptions extends Connection { // This will build an inverted array of inherited classes ([grandparent, parent, item]) function getInheritanceTree(model: Function): Function[] { const tree = [model]; - function getRecursivePrototypesOf(parentEntity: Function) { + function getRecursivePrototypesOf(parentEntity: Function): void { const proto = Object.getPrototypeOf(parentEntity); if (proto && proto.name && proto.name !== 'Function') { tree.unshift(proto); @@ -83,7 +83,7 @@ export function initialize({ modelMetadataByModelName[model.name] = model; } - type ColumnsByPropertyName = { [index: string]: ColumnMetadata }; + interface ColumnsByPropertyName { [index: string]: ColumnMetadata } // Add dictionary to quickly find a column by propertyName, for applying ColumnModifierMetadata records const columnsByPropertyNameForModel: { [index: string]: ColumnsByPropertyName } = {}; for (const column of metadataStorage.columns) { @@ -91,7 +91,7 @@ export function initialize({ columnsByPropertyNameForModel[column.target][column.propertyName] = column; } - type ColumnModifiersByPropertyName = { [index: string]: ColumnModifierMetadata[] }; + interface ColumnModifiersByPropertyName { [index: string]: ColumnModifierMetadata[] } const columnModifiersByPropertyNameForModel: { [index: string]: ColumnModifiersByPropertyName } = {}; for (const columnModifier of metadataStorage.columnModifiers) { columnModifiersByPropertyNameForModel[columnModifier.target] = columnModifiersByPropertyNameForModel[columnModifier.target] || {}; diff --git a/src/metadata/ModelMetadata.ts b/src/metadata/ModelMetadata.ts index 9526106..8ee35dd 100644 --- a/src/metadata/ModelMetadata.ts +++ b/src/metadata/ModelMetadata.ts @@ -16,6 +16,16 @@ export interface ModelMetadataOptions { } export class ModelMetadata { + private _columns: readonly Column[] = []; + + private _primaryKeyColumn: Column | undefined; + + private _createDateColumns: Column[] = []; + + private _updateDateColumns: Column[] = []; + + private _versionDateColumns: Column[] = []; + public set columns(columns: readonly Column[]) { this._columns = columns; this.columnsByColumnName = {}; @@ -51,15 +61,15 @@ export class ModelMetadata { return this._primaryKeyColumn; } - public get createDateColumns(): ReadonlyArray { + public get createDateColumns(): readonly Column[] { return this._createDateColumns; } - public get updateDateColumns(): ReadonlyArray { + public get updateDateColumns(): readonly Column[] { return this._updateDateColumns; } - public get versionColumns(): ReadonlyArray { + public get versionColumns(): readonly Column[] { return this._versionDateColumns; } @@ -77,16 +87,6 @@ export class ModelMetadata { public columnsByPropertyName: ColumnByStringId = {}; - private _columns: readonly Column[] = []; - - private _primaryKeyColumn: Column | undefined; - - private _createDateColumns: Column[] = []; - - private _updateDateColumns: Column[] = []; - - private _versionDateColumns: Column[] = []; - public constructor({ name, type, diff --git a/src/query/CountResult.ts b/src/query/CountResult.ts index 40329f9..2e0720b 100644 --- a/src/query/CountResult.ts +++ b/src/query/CountResult.ts @@ -1,7 +1,6 @@ import { WhereQuery } from './WhereQuery'; +import { ChainablePromiseLike } from '../ChainablePromiseLike'; -export interface CountResult { +export interface CountResult extends ChainablePromiseLike { where(args: WhereQuery): CountResult | number; - then(onFulfill: (value: number) => U | PromiseLike, onReject?: (error: Error) => U | PromiseLike): Promise; - then(onFulfill: (value: number) => U | PromiseLike, onReject?: (error: Error) => void | PromiseLike): Promise; } diff --git a/src/query/DestroyResult.ts b/src/query/DestroyResult.ts index 88c597c..ed7a88b 100644 --- a/src/query/DestroyResult.ts +++ b/src/query/DestroyResult.ts @@ -1,7 +1,6 @@ import { WhereQuery } from './WhereQuery'; +import { ChainablePromiseLike } from '../ChainablePromiseLike'; -export interface DestroyResult { +export interface DestroyResult extends ChainablePromiseLike { where(args: WhereQuery): DestroyResult; - then(onFulfill: (value: TReturn) => U | PromiseLike, onReject?: (error: Error) => U | PromiseLike): Promise; - then(onFulfill: (value: TReturn) => U | PromiseLike, onReject?: (error: Error) => void | PromiseLike): Promise; } diff --git a/src/query/FindOneResult.ts b/src/query/FindOneResult.ts index 2a1988a..c0c36d8 100644 --- a/src/query/FindOneResult.ts +++ b/src/query/FindOneResult.ts @@ -1,11 +1,10 @@ import { WhereQuery } from './WhereQuery'; import { PopulateArgs } from './PopulateArgs'; import { Entity } from '../Entity'; +import { ChainablePromiseLike } from '../ChainablePromiseLike'; -export interface FindOneResult { +export interface FindOneResult extends ChainablePromiseLike { where(args: WhereQuery): FindOneResult; populate(propertyName: Extract, options?: PopulateArgs): FindOneResult; sort(value: string | object): FindOneResult; - then(onFulfill: (value: TEntity | null) => U | PromiseLike, onReject?: (error: Error) => U | PromiseLike): Promise; - then(onFulfill: (value: TEntity | null) => U | PromiseLike, onReject?: (error: Error) => void | PromiseLike): Promise; } diff --git a/src/query/FindResult.ts b/src/query/FindResult.ts index c20fbfc..6641355 100644 --- a/src/query/FindResult.ts +++ b/src/query/FindResult.ts @@ -1,12 +1,11 @@ import { PaginateOptions } from './PaginateOptions'; import { WhereQuery } from './WhereQuery'; +import { ChainablePromiseLike } from '../ChainablePromiseLike'; -export interface FindResult { +export interface FindResult extends ChainablePromiseLike { where(args: WhereQuery): FindResult; sort(value: string | object): FindResult; limit(value: number): FindResult; skip(value: number): FindResult; paginate(options: PaginateOptions): FindResult; - then(onFulfill: (value: TEntity[]) => U | PromiseLike, onReject?: (error: Error) => U | PromiseLike): Promise; - then(onFulfill: (value: TEntity[]) => U | PromiseLike, onReject?: (error: Error) => void | PromiseLike): Promise; } diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index 4368e5b..9fe7f2d 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -14,5 +14,7 @@ module.exports = { }, rules: { 'max-classes-per-file': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/unbound-method': 'off', }, }; diff --git a/tests/models/ProductWithCreateUpdateDateTracking.ts b/tests/models/ProductWithCreateUpdateDateTracking.ts index bac0f32..1424d86 100644 --- a/tests/models/ProductWithCreateUpdateDateTracking.ts +++ b/tests/models/ProductWithCreateUpdateDateTracking.ts @@ -1,7 +1,7 @@ import { Product } from './Product'; export class ProductWithCreateUpdateDateTracking extends Product { - public static async beforeCreate(values: Partial) { + public static async beforeCreate(values: Partial): Promise> { await Promise.resolve(); return { @@ -10,7 +10,7 @@ export class ProductWithCreateUpdateDateTracking extends Product { }; } - public static beforeUpdate(values: Partial) { + public static beforeUpdate(values: Partial): Partial { return { ...values, name: `beforeUpdate - ${values.name}`, diff --git a/tests/readonlyRepository.tests.ts b/tests/readonlyRepository.tests.ts index d230f16..82cee95 100644 --- a/tests/readonlyRepository.tests.ts +++ b/tests/readonlyRepository.tests.ts @@ -2,6 +2,7 @@ import chai from 'chai'; import _ from 'lodash'; import * as faker from 'faker'; import { Pool } from 'postgres-pool'; +import { QueryResult } from 'pg'; import { anyString, anything, @@ -28,8 +29,7 @@ import { import { ColumnTypeMetadata, ModelMetadata } from '../src/metadata'; import { RepositoriesByModelNameLowered } from '../src/RepositoriesByModelNameLowered'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function getQueryResult(rows: any[] = []) { +function getQueryResult(rows: T[] = []): QueryResult { return { command: 'select', rowCount: 1, @@ -69,7 +69,7 @@ describe('ReadonlyRepository', () => { StoreRepository = repositoriesByModelName.Store as Repository; }); - beforeEach(async () => { + beforeEach(() => { reset(mockedPool); }); @@ -83,7 +83,6 @@ describe('ReadonlyRepository', () => { when(mockedPool.query(anyString(), anything())).thenResolve(getQueryResult([product])); const result = await ReadonlyProductRepository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -91,7 +90,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "readonly_products" LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support call with constraints as a parameter', async () => { @@ -110,7 +108,6 @@ describe('ReadonlyRepository', () => { sort: 'name asc', }); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -118,7 +115,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "name","id" FROM "products" WHERE "id"=$1 ORDER BY "name" LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([product.id]); }); it('should support call with where constraint as a parameter', async () => { @@ -133,7 +129,6 @@ describe('ReadonlyRepository', () => { id: product.id, }); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -141,7 +136,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([product.id]); }); it('should support call with chained where constraints', async () => { @@ -156,7 +150,6 @@ describe('ReadonlyRepository', () => { id: product.id, }); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -164,7 +157,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([product.id]); }); it('should support call with chained where constraints - Promise.all', async () => { @@ -183,7 +175,6 @@ describe('ReadonlyRepository', () => { }), ]); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -191,7 +182,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=$1 LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([product.id]); }); it('should support call with chained sort', async () => { @@ -204,7 +194,6 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne().sort('name asc'); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -212,7 +201,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" ORDER BY "name" LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); describe('Parse number columns', () => { @@ -255,7 +243,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: numberValue, @@ -266,7 +253,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should parse integer columns from float strings query value', async () => { @@ -307,7 +293,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: 42, @@ -318,7 +303,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should parse integer columns that return as number', async () => { @@ -359,7 +343,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: numberValue, @@ -370,7 +353,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should ignore large integer columns values', async () => { @@ -411,7 +393,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: largeNumberValue, @@ -422,7 +403,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should parse float columns return as float strings', async () => { @@ -463,7 +443,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: numberValue, @@ -474,7 +453,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should parse float columns return as number', async () => { @@ -515,7 +493,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: numberValue, @@ -526,7 +503,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should ignore large float columns', async () => { @@ -567,7 +543,6 @@ describe('ReadonlyRepository', () => { const result = await repository.findOne(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal({ id, foo: largeNumberValue, @@ -578,7 +553,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal(`SELECT "id","foo" FROM "${model.tableName}" LIMIT 1`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); }); @@ -605,7 +579,6 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne().populate('store'); verify(mockedPool.query(anyString(), anything())).twice(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(product); const [ @@ -613,14 +586,12 @@ describe('ReadonlyRepository', () => { productQueryParams, ] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion productQueryParams!.should.deep.equal([]); const [ storeQuery, storeQueryParams, ] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1 LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion storeQueryParams!.should.deep.equal([store.id]); }); it('should support populating collection', async () => { @@ -651,7 +622,6 @@ describe('ReadonlyRepository', () => { const result = await StoreRepository.findOne().populate('products'); verify(mockedPool.query(anyString(), anything())).twice(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(storeWithProducts); const [ @@ -659,14 +629,12 @@ describe('ReadonlyRepository', () => { storeQueryParams, ] = capture(mockedPool.query).first(); storeQuery.should.equal('SELECT "id","name" FROM "stores" LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion storeQueryParams!.should.deep.equal([]); const [ productQuery, productQueryParams, ] = capture(mockedPool.query).second(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion productQueryParams!.should.deep.equal([store.id]); }); it('should support populating multi-multi collection', async () => { @@ -706,7 +674,6 @@ describe('ReadonlyRepository', () => { const result = await ProductRepository.findOne().populate('categories'); verify(mockedPool.query(anyString(), anything())).thrice(); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(productWithCategories); const [ @@ -714,21 +681,18 @@ describe('ReadonlyRepository', () => { productQueryParams, ] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 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'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion productCategoryMapQueryParams!.should.deep.equal([product.id]); const [ categoryQuery, categoryQueryParams, ] = capture(mockedPool.query).third(); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[])'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion categoryQueryParams!.should.deep.equal([ [category1.id, category2.id], ]); @@ -791,7 +755,6 @@ describe('ReadonlyRepository', () => { .sort('store desc'); verify(mockedPool.query(anyString(), anything())).times(4); should.exist(result); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result!.should.deep.equal(fullProduct); const [ @@ -799,28 +762,24 @@ describe('ReadonlyRepository', () => { productQueryParams, ] = capture(mockedPool.query).first(); productQuery.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 ORDER BY "store_id" DESC LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion productQueryParams!.should.deep.equal([store.id]); const [ storeQuery, storeQueryParams, ] = capture(mockedPool.query).second(); storeQuery.should.equal('SELECT "id","name" FROM "stores" WHERE "id"=$1 LIMIT 1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion storeQueryParams!.should.deep.equal([store.id]); const [ productCategoryMapQuery, productCategoryMapQueryParams, ] = capture(mockedPool.query).third(); productCategoryMapQuery.should.equal('SELECT "category_id" AS "category","id" FROM "product__category" WHERE "product_id"=$1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion productCategoryMapQueryParams!.should.deep.equal([product.id]); const [ categoryQuery, categoryQueryParams, ] = capture(mockedPool.query).byCallIndex(3); categoryQuery.should.equal('SELECT "id","name" FROM "categories" WHERE "id"=ANY($1::INTEGER[]) AND "name" ILIKE $2 ORDER BY "name" LIMIT 2'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion categoryQueryParams!.should.deep.equal([ [category1.id, category2.id], 'category%', @@ -833,7 +792,7 @@ describe('ReadonlyRepository', () => { public foo: string | undefined; - public toBar() { + public toBar(): string { return `${this.foo} bar!`; } } @@ -880,12 +839,9 @@ describe('ReadonlyRepository', () => { verify(mockedPool.query(anyString(), anything())).twice(); should.exist(result1); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result1!.should.deep.equal(result2); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result1!.toBar().should.equal(`${foo} bar!`); should.exist(result2); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result2!.toBar().should.equal(`${foo} bar!`); }); it('should not create an object/assign instance functions to null results', async () => { @@ -895,7 +851,7 @@ describe('ReadonlyRepository', () => { public foo: string | undefined; - public toBar() { + public toBar(): string { return `${this.foo} bar!`; } } @@ -960,7 +916,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products"'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support call with constraints as a parameter', async () => { @@ -997,7 +952,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "name","id" FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2 ORDER BY "name" LIMIT 24 OFFSET 5'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([ _.map(products, 'id'), store.id, @@ -1031,7 +985,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([ _.map(products, 'id'), store.id, @@ -1064,7 +1017,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([store.id]); }); it('should support call with chained where constraints - array ILIKE array of values', async () => { @@ -1103,7 +1055,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE (("name" ILIKE $1) OR ("name" ILIKE $2)) AND EXISTS(SELECT 1 FROM (SELECT unnest("alias_names") AS "unnested_alias_names") __unnested WHERE lower("unnested_alias_names")=ANY($3::TEXT[]))'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([ 'product', 'Foo Bar', @@ -1139,7 +1090,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE lower("sku")<>ALL($1::TEXT[])'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([['foo', 'bar']]); }); it('should support call with chained where constraints - Promise.all', async () => { @@ -1173,7 +1123,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([store.id]); }); it('should support call with chained sort', async () => { @@ -1197,7 +1146,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" ORDER BY "name"'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support call with chained limit', async () => { @@ -1221,7 +1169,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 42'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support call with chained skip', async () => { @@ -1245,7 +1192,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" OFFSET 24'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support call with chained paginate', async () => { @@ -1272,7 +1218,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" LIMIT 100 OFFSET 200'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support complex query with multiple chained modifiers', async () => { @@ -1309,7 +1254,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT "id","name","sku","alias_names" AS "aliases","store_id" AS "store" FROM "products" WHERE "store_id"=$1 ORDER BY "store_id" DESC LIMIT 42 OFFSET 24'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([store.id]); }); it('should have instance functions be equal across multiple queries', async () => { @@ -1319,7 +1263,7 @@ describe('ReadonlyRepository', () => { public foo: string | undefined; - public toBar() { + public toBar(): string { return `${this.foo} bar!`; } } @@ -1366,10 +1310,8 @@ describe('ReadonlyRepository', () => { should.exist(result1); should.exist(result2); result1.should.deep.equal(result2); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - result1![0].toBar().should.equal(`${foo} bar!`); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - result2![0].toBar().should.equal(`${foo} bar!`); + result1[0].toBar().should.equal(`${foo} bar!`); + result2[0].toBar().should.equal(`${foo} bar!`); }); }); describe('#count()', () => { @@ -1397,7 +1339,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products"'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([]); }); it('should support call constraints as a parameter', async () => { @@ -1431,7 +1372,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products" WHERE "id"=ANY($1::INTEGER[]) AND "store_id"=$2'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([ _.map(products, 'id'), store.id, @@ -1467,7 +1407,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products" WHERE "store_id"=$1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([store.id]); }); it('should support call with chained where constraints - Promise.all', async () => { @@ -1504,7 +1443,6 @@ describe('ReadonlyRepository', () => { params, ] = capture(mockedPool.query).first(); query.should.equal('SELECT count(*) AS "count" FROM "products" WHERE "store_id"=$1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion params!.should.deep.equal([store.id]); }); }); diff --git a/tests/repository.tests.ts b/tests/repository.tests.ts index 545d7dc..c7a76bd 100644 --- a/tests/repository.tests.ts +++ b/tests/repository.tests.ts @@ -2,6 +2,7 @@ import chai from 'chai'; import * as _ from 'lodash'; import * as faker from 'faker'; import { Pool } from 'postgres-pool'; +import { QueryResult } from 'pg'; import { anyString, anything, @@ -25,8 +26,7 @@ import { import { ColumnTypeMetadata, ModelMetadata } from '../src/metadata'; import { RepositoriesByModelNameLowered } from '../src/RepositoriesByModelNameLowered'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function getQueryResult(rows: any[] = []) { +function getQueryResult(rows: T[] = []): QueryResult { return { command: 'select', rowCount: 1, @@ -62,7 +62,7 @@ describe('Repository', () => { ProductWithCreateUpdateDateTrackingRepository = repositoriesByModelName.ProductWithCreateUpdateDateTracking as Repository; }); - beforeEach(async () => { + beforeEach(() => { reset(mockedPool); }); diff --git a/tests/sqlHelper.tests.ts b/tests/sqlHelper.tests.ts index b91ddbc..094dd94 100644 --- a/tests/sqlHelper.tests.ts +++ b/tests/sqlHelper.tests.ts @@ -397,7 +397,7 @@ describe('sqlHelper', () => { }); describe('#getInsertQueryAndParams()', () => { it('should throw if a required property has an undefined value', () => { - (() => { + ((): void => { sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered, model: repositoriesByModelNameLowered.product.model, @@ -444,7 +444,7 @@ describe('sqlHelper', () => { repositoriesByModelNameLowered: repositories, }); - (() => { + ((): void => { sqlHelper.getInsertQueryAndParams({ repositoriesByModelNameLowered: repositories, model, @@ -566,7 +566,7 @@ describe('sqlHelper', () => { target: 'foo', name: 'name', propertyName: 'name', - defaultsTo: () => 'foobar', + defaultsTo: (): string => 'foobar', type: 'string', }), new ColumnTypeMetadata({ @@ -1789,11 +1789,12 @@ describe('sqlHelper', () => { params.should.deep.equal([]); }); it('should throw if query value is undefined', () => { - (() => { + ((): void => { sqlHelper._buildWhereStatement({ repositoriesByModelNameLowered, model: repositoriesByModelNameLowered.product.model, - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore - testing a value not allowed by type definition where: { store: undefined, }, @@ -3429,7 +3430,8 @@ describe('sqlHelper', () => { it('should return empty if there are orders is null', () => { const result = sqlHelper._buildOrderStatement({ model, - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore - testing a value that isn't allowed by typescript sorts: null, });