From 16a8f5b13cf55986464996fe075cac72e8d1cd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Pereyra?= Date: Fri, 31 May 2019 15:03:23 -0300 Subject: [PATCH 01/35] Moved QueryBuilder to a separte package --- .nycrc | 3 +- mysql/mysql.js | 3 +- package-lock.json | 1562 +++++++++++++------------- package.json | 3 +- query-builder/index.js | 9 - query-builder/query-builder-error.js | 12 - query-builder/query-builder.js | 1108 ------------------ tests/mysql-test.js | 5 +- tests/query-builder-test.js | 1548 ------------------------- 9 files changed, 792 insertions(+), 3461 deletions(-) delete mode 100644 query-builder/index.js delete mode 100644 query-builder/query-builder-error.js delete mode 100644 query-builder/query-builder.js delete mode 100644 tests/query-builder-test.js diff --git a/.nycrc b/.nycrc index bc47737..a78e273 100644 --- a/.nycrc +++ b/.nycrc @@ -1,6 +1,7 @@ { "exclude": [ - "tests/" + "tests/", + ".eslintrc.js" ], "extension": [ ".js" diff --git a/mysql/mysql.js b/mysql/mysql.js index be8abbe..6ec7462 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -4,13 +4,12 @@ const mysql = require('mysql2'); const knex = require('knex'); const logger = require('@janiscommerce/logger'); +const QueryBuilder = require('@janiscommerce/query-builder'); const MySQLError = require('./mysql-error'); const Utils = require('./../utils'); -const { QueryBuilder } = require('./../query-builder'); - const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds const CONNECTION_LIMIT = 10; diff --git a/package-lock.json b/package-lock.json index 9eb7e3c..1f49873 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "mysql", + "name": "@janiscommerce/mysql", "version": "1.0.0", "lockfileVersion": 1, "requires": true, @@ -10,7 +10,7 @@ "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "@babel/highlight": "7.0.0" + "@babel/highlight": "^7.0.0" } }, "@babel/generator": { @@ -19,11 +19,11 @@ "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", "dev": true, "requires": { - "@babel/types": "7.4.4", - "jsesc": "2.5.2", - "lodash": "4.17.11", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" } }, "@babel/helper-function-name": { @@ -32,9 +32,9 @@ "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.0.0", - "@babel/template": "7.4.4", - "@babel/types": "7.4.4" + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, "@babel/helper-get-function-arity": { @@ -43,7 +43,7 @@ "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", "dev": true, "requires": { - "@babel/types": "7.4.4" + "@babel/types": "^7.0.0" } }, "@babel/helper-split-export-declaration": { @@ -52,7 +52,7 @@ "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", "dev": true, "requires": { - "@babel/types": "7.4.4" + "@babel/types": "^7.4.4" } }, "@babel/highlight": { @@ -61,9 +61,9 @@ "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { - "chalk": "2.4.2", - "esutils": "2.0.2", - "js-tokens": "4.0.0" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { @@ -77,8 +77,8 @@ "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", "requires": { - "core-js": "2.6.5", - "regenerator-runtime": "0.13.2" + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" } }, "@babel/template": { @@ -87,9 +87,9 @@ "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0", - "@babel/parser": "7.4.4", - "@babel/types": "7.4.4" + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" } }, "@babel/traverse": { @@ -98,15 +98,15 @@ "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0", - "@babel/generator": "7.4.4", - "@babel/helper-function-name": "7.1.0", - "@babel/helper-split-export-declaration": "7.4.4", - "@babel/parser": "7.4.4", - "@babel/types": "7.4.4", - "debug": "4.1.1", - "globals": "11.12.0", - "lodash": "4.17.11" + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" } }, "@babel/types": { @@ -115,9 +115,9 @@ "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", "dev": true, "requires": { - "esutils": "2.0.2", - "lodash": "4.17.11", - "to-fast-properties": "2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" } }, "@janiscommerce/logger": { @@ -125,9 +125,15 @@ "resolved": "https://registry.npmjs.org/@janiscommerce/logger/-/logger-1.0.2.tgz", "integrity": "sha512-k/roQD+pp9+F6luKBT+82hq0e0JLv0OwWaqxQyt6SIfvS9wR5PEe1QMIqAue0m5UyNFcNtoFTLD63xV0cqrQrw==", "requires": { - "cli-color": "1.4.0", - "winston": "3.2.1", - "winston-transport": "4.3.0" + "cli-color": "^1.4.0", + "winston": "^3.2.1", + "winston-transport": "^4.3.0" + } + }, + "@janiscommerce/query-builder": { + "version": "file:../query-builder", + "requires": { + "@janiscommerce/logger": "^1.0.2" } }, "@sinonjs/commons": { @@ -145,8 +151,8 @@ "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", "dev": true, "requires": { - "@sinonjs/commons": "1.4.0", - "@sinonjs/samsam": "3.3.1" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, "@sinonjs/samsam": { @@ -155,9 +161,9 @@ "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", "dev": true, "requires": { - "@sinonjs/commons": "1.4.0", - "array-from": "2.1.1", - "lodash": "4.17.11" + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" } }, "@sinonjs/text-encoding": { @@ -189,10 +195,10 @@ "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "dev": true, "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-escapes": { @@ -213,7 +219,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.3" + "color-convert": "^1.9.0" } }, "ansicolors": { @@ -227,7 +233,7 @@ "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "default-require-extensions": "2.0.0" + "default-require-extensions": "^2.0.0" } }, "archy": { @@ -242,7 +248,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -277,8 +283,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.13.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "array-slice": { @@ -307,7 +313,7 @@ "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "requires": { - "lodash": "4.17.11" + "lodash": "^4.17.11" } }, "atob": { @@ -326,13 +332,13 @@ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.3.0", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { "define-property": { @@ -340,7 +346,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -348,7 +354,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -356,7 +362,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -364,9 +370,9 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -382,7 +388,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -391,16 +397,16 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.3", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -408,7 +414,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -424,15 +430,15 @@ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.3.0", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" } }, "caching-transform": { @@ -441,10 +447,10 @@ "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", "dev": true, "requires": { - "hasha": "3.0.0", - "make-dir": "2.1.0", - "package-hash": "3.0.0", - "write-file-atomic": "2.4.2" + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" } }, "callsites": { @@ -464,8 +470,8 @@ "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz", "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=", "requires": { - "ansicolors": "0.2.1", - "redeyed": "1.0.1" + "ansicolors": "~0.2.1", + "redeyed": "~1.0.0" } }, "chalk": { @@ -474,9 +480,9 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chardet": { @@ -490,10 +496,10 @@ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { "define-property": { @@ -501,7 +507,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -511,12 +517,12 @@ "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", "requires": { - "ansi-regex": "2.1.1", - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-iterator": "2.0.3", - "memoizee": "0.4.14", - "timers-ext": "0.1.7" + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" }, "dependencies": { "ansi-regex": { @@ -532,7 +538,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -547,9 +553,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, "code-point-at": { @@ -563,8 +569,8 @@ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color": { @@ -572,8 +578,8 @@ "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", "requires": { - "color-convert": "1.9.3", - "color-string": "1.5.3" + "color-convert": "^1.9.1", + "color-string": "^1.5.2" } }, "color-convert": { @@ -594,8 +600,8 @@ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", "requires": { - "color-name": "1.1.3", - "simple-swizzle": "0.2.2" + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" } }, "colorette": { @@ -618,8 +624,8 @@ "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", "requires": { - "color": "3.0.0", - "text-hex": "1.0.0" + "color": "3.0.x", + "text-hex": "1.0.x" } }, "commander": { @@ -657,7 +663,7 @@ "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.1" } }, "copy-descriptor": { @@ -681,11 +687,11 @@ "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", "dev": true, "requires": { - "graceful-fs": "4.1.15", - "make-dir": "2.1.0", - "nested-error-stacks": "2.1.0", - "pify": "4.0.1", - "safe-buffer": "5.1.2" + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" }, "dependencies": { "pify": { @@ -702,11 +708,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.7.0", - "shebang-command": "1.2.0", - "which": "1.3.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "d": { @@ -714,7 +720,7 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "es5-ext": "0.10.50" + "es5-ext": "^0.10.9" } }, "debug": { @@ -722,7 +728,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "decamelize": { @@ -748,7 +754,7 @@ "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "strip-bom": "3.0.0" + "strip-bom": "^3.0.0" } }, "define-properties": { @@ -757,7 +763,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "1.1.1" + "object-keys": "^1.0.12" } }, "define-property": { @@ -765,8 +771,8 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -774,7 +780,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -782,7 +788,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -790,9 +796,9 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -812,9 +818,9 @@ "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", "requires": { - "colorspace": "1.1.2", - "enabled": "1.0.2", - "kuler": "1.0.1" + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" } }, "diff": { @@ -829,7 +835,7 @@ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { - "esutils": "2.0.2" + "esutils": "^2.0.2" } }, "emoji-regex": { @@ -843,7 +849,7 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", "requires": { - "env-variable": "0.0.5" + "env-variable": "0.0.x" } }, "end-of-stream": { @@ -852,7 +858,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "env-variable": { @@ -866,7 +872,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { @@ -875,12 +881,12 @@ "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", "dev": true, "requires": { - "es-to-primitive": "1.2.0", - "function-bind": "1.1.1", - "has": "1.0.3", - "is-callable": "1.1.4", - "is-regex": "1.0.4", - "object-keys": "1.1.1" + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" } }, "es-to-primitive": { @@ -889,9 +895,9 @@ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "1.1.4", - "is-date-object": "1.0.1", - "is-symbol": "1.0.2" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "es5-ext": { @@ -899,9 +905,9 @@ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" } }, "es6-error": { @@ -915,9 +921,9 @@ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-symbol": { @@ -925,8 +931,8 @@ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50" + "d": "1", + "es5-ext": "~0.10.14" } }, "es6-weak-map": { @@ -934,10 +940,10 @@ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" } }, "escape-string-regexp": { @@ -952,42 +958,42 @@ "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0", - "ajv": "6.10.0", - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "debug": "4.1.1", - "doctrine": "3.0.0", - "eslint-scope": "4.0.3", - "eslint-utils": "1.3.1", - "eslint-visitor-keys": "1.0.0", - "espree": "5.0.1", - "esquery": "1.0.1", - "esutils": "2.0.2", - "file-entry-cache": "5.0.1", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.3", - "globals": "11.12.0", - "ignore": "4.0.6", - "import-fresh": "3.0.0", - "imurmurhash": "0.1.4", - "inquirer": "6.3.1", - "js-yaml": "3.13.1", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "progress": "2.0.3", - "regexpp": "2.0.1", - "semver": "5.7.0", - "strip-ansi": "4.0.0", - "strip-json-comments": "2.0.1", - "table": "5.2.3", - "text-table": "0.2.0" + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" } }, "eslint-config-airbnb-base": { @@ -996,9 +1002,9 @@ "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", "dev": true, "requires": { - "eslint-restricted-globals": "0.1.1", - "object.assign": "4.1.0", - "object.entries": "1.1.0" + "eslint-restricted-globals": "^0.1.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" } }, "eslint-import-resolver-node": { @@ -1007,8 +1013,8 @@ "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { - "debug": "2.6.9", - "resolve": "1.10.1" + "debug": "^2.6.9", + "resolve": "^1.5.0" }, "dependencies": { "debug": { @@ -1034,8 +1040,8 @@ "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", "dev": true, "requires": { - "debug": "2.6.9", - "pkg-dir": "2.0.0" + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" }, "dependencies": { "debug": { @@ -1061,17 +1067,17 @@ "integrity": "sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g==", "dev": true, "requires": { - "array-includes": "3.0.3", - "contains-path": "0.1.0", - "debug": "2.6.9", + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "0.3.2", - "eslint-module-utils": "2.4.0", - "has": "1.0.3", - "lodash": "4.17.11", - "minimatch": "3.0.4", - "read-pkg-up": "2.0.0", - "resolve": "1.10.1" + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "read-pkg-up": "^2.0.0", + "resolve": "^1.10.0" }, "dependencies": { "debug": { @@ -1089,8 +1095,8 @@ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } }, "ms": { @@ -1113,8 +1119,8 @@ "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint-utils": { @@ -1135,9 +1141,9 @@ "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", "dev": true, "requires": { - "acorn": "6.1.1", - "acorn-jsx": "5.0.1", - "eslint-visitor-keys": "1.0.0" + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" } }, "esprima": { @@ -1151,7 +1157,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { @@ -1160,7 +1166,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -1180,8 +1186,8 @@ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50" + "d": "1", + "es5-ext": "~0.10.14" } }, "execa": { @@ -1190,13 +1196,13 @@ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "6.0.5", - "get-stream": "4.1.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "expand-brackets": { @@ -1204,13 +1210,13 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "debug": { @@ -1226,7 +1232,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -1234,7 +1240,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "ms": { @@ -1249,7 +1255,7 @@ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "requires": { - "homedir-polyfill": "1.0.3" + "homedir-polyfill": "^1.0.1" } }, "extend": { @@ -1262,8 +1268,8 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -1271,7 +1277,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -1282,9 +1288,9 @@ "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "0.7.0", - "iconv-lite": "0.4.24", - "tmp": "0.0.33" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" } }, "extglob": { @@ -1292,14 +1298,14 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -1307,7 +1313,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -1315,7 +1321,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -1323,7 +1329,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -1331,7 +1337,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -1339,9 +1345,9 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -1380,7 +1386,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -1389,7 +1395,7 @@ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "2.0.1" + "flat-cache": "^2.0.1" } }, "fill-range": { @@ -1397,10 +1403,10 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -1408,7 +1414,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -1419,9 +1425,9 @@ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "2.1.0", - "pkg-dir": "3.0.0" + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" }, "dependencies": { "find-up": { @@ -1430,7 +1436,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { @@ -1439,8 +1445,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -1449,7 +1455,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -1458,7 +1464,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -1473,7 +1479,7 @@ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "3.0.0" + "find-up": "^3.0.0" } } } @@ -1484,7 +1490,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "findup-sync": { @@ -1492,10 +1498,10 @@ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", "requires": { - "detect-file": "1.0.0", - "is-glob": "4.0.1", - "micromatch": "3.1.10", - "resolve-dir": "1.0.1" + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" } }, "fined": { @@ -1503,11 +1509,11 @@ "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", "requires": { - "expand-tilde": "2.0.2", - "is-plain-object": "2.0.4", - "object.defaults": "1.1.0", - "object.pick": "1.3.0", - "parse-filepath": "1.0.2" + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" } }, "flagged-respawn": { @@ -1521,7 +1527,7 @@ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "flatted": "2.0.0", + "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" } @@ -1542,7 +1548,7 @@ "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "foreground-child": { @@ -1551,8 +1557,8 @@ "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { - "cross-spawn": "4.0.2", - "signal-exit": "3.0.2" + "cross-spawn": "^4", + "signal-exit": "^3.0.0" }, "dependencies": { "cross-spawn": { @@ -1561,8 +1567,8 @@ "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { - "lru-cache": "4.1.5", - "which": "1.3.1" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } } } @@ -1572,7 +1578,7 @@ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "requires": { - "map-cache": "0.2.2" + "map-cache": "^0.2.2" } }, "fs.realpath": { @@ -1598,7 +1604,7 @@ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "requires": { - "is-property": "1.0.2" + "is-property": "^1.0.2" } }, "get-caller-file": { @@ -1613,7 +1619,7 @@ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "get-value": { @@ -1632,12 +1638,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "global-modules": { @@ -1645,9 +1651,9 @@ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "requires": { - "global-prefix": "1.0.2", - "is-windows": "1.0.2", - "resolve-dir": "1.0.1" + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" } }, "global-prefix": { @@ -1655,11 +1661,11 @@ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "requires": { - "expand-tilde": "2.0.2", - "homedir-polyfill": "1.0.3", - "ini": "1.3.5", - "is-windows": "1.0.2", - "which": "1.3.1" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" } }, "globals": { @@ -1686,10 +1692,10 @@ "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "dev": true, "requires": { - "neo-async": "2.6.0", - "optimist": "0.6.1", - "source-map": "0.6.1", - "uglify-js": "3.5.11" + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" }, "dependencies": { "source-map": { @@ -1706,7 +1712,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-flag": { @@ -1726,9 +1732,9 @@ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" } }, "has-values": { @@ -1736,8 +1742,8 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "kind-of": { @@ -1745,7 +1751,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -1756,7 +1762,7 @@ "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", "dev": true, "requires": { - "is-stream": "1.1.0" + "is-stream": "^1.0.1" } }, "he": { @@ -1770,7 +1776,7 @@ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "requires": { - "parse-passwd": "1.0.0" + "parse-passwd": "^1.0.0" } }, "hosted-git-info": { @@ -1784,7 +1790,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "ignore": { @@ -1799,8 +1805,8 @@ "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", "dev": true, "requires": { - "parent-module": "1.0.1", - "resolve-from": "4.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, "imurmurhash": { @@ -1815,8 +1821,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -1835,19 +1841,19 @@ "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", "dev": true, "requires": { - "ansi-escapes": "3.2.0", - "chalk": "2.4.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "3.0.3", - "figures": "2.0.0", - "lodash": "4.17.11", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "6.5.1", - "string-width": "2.1.1", - "strip-ansi": "5.2.0", - "through": "2.3.8" + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" }, "dependencies": { "ansi-regex": { @@ -1862,7 +1868,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -1883,8 +1889,8 @@ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "requires": { - "is-relative": "1.0.0", - "is-windows": "1.0.2" + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" } }, "is-accessor-descriptor": { @@ -1892,7 +1898,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -1900,7 +1906,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -1927,7 +1933,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -1935,7 +1941,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -1951,9 +1957,9 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { "kind-of": { @@ -1984,7 +1990,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -1992,7 +1998,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -2000,7 +2006,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -2010,7 +2016,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "is-promise": { @@ -2029,7 +2035,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.3" + "has": "^1.0.1" } }, "is-relative": { @@ -2037,7 +2043,7 @@ "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "requires": { - "is-unc-path": "1.0.0" + "is-unc-path": "^1.0.0" } }, "is-stream": { @@ -2051,7 +2057,7 @@ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "dev": true, "requires": { - "has-symbols": "1.0.0" + "has-symbols": "^1.0.0" } }, "is-unc-path": { @@ -2059,7 +2065,7 @@ "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "requires": { - "unc-path-regex": "0.1.2" + "unc-path-regex": "^0.1.2" } }, "is-windows": { @@ -2094,7 +2100,7 @@ "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", "dev": true, "requires": { - "append-transform": "1.0.0" + "append-transform": "^1.0.0" } }, "istanbul-lib-instrument": { @@ -2103,13 +2109,13 @@ "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "requires": { - "@babel/generator": "7.4.4", - "@babel/parser": "7.4.4", - "@babel/template": "7.4.4", - "@babel/traverse": "7.4.4", - "@babel/types": "7.4.4", - "istanbul-lib-coverage": "2.0.5", - "semver": "6.0.0" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" }, "dependencies": { "semver": { @@ -2126,9 +2132,9 @@ "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "requires": { - "istanbul-lib-coverage": "2.0.5", - "make-dir": "2.1.0", - "supports-color": "6.1.0" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "dependencies": { "supports-color": { @@ -2137,7 +2143,7 @@ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -2148,11 +2154,11 @@ "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { - "debug": "4.1.1", - "istanbul-lib-coverage": "2.0.5", - "make-dir": "2.1.0", - "rimraf": "2.6.3", - "source-map": "0.6.1" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -2169,7 +2175,7 @@ "integrity": "sha512-QCHGyZEK0bfi9GR215QSm+NJwFKEShbtc7tfbUdLAEzn3kKhLDDZqvljn8rPZM9v8CEOhzL1nlYoO4r1ryl67w==", "dev": true, "requires": { - "handlebars": "4.1.2" + "handlebars": "^4.1.2" } }, "js-tokens": { @@ -2184,8 +2190,8 @@ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "dependencies": { "esprima": { @@ -2236,23 +2242,23 @@ "resolved": "https://registry.npmjs.org/knex/-/knex-0.16.5.tgz", "integrity": "sha512-1RVxMU8zGOBqgmXlAvs8vohg9MD14iiRZZPe0IeQXd554n4xxPmoMkbH4hlFeqfM6eOdFE3AVqVSncL3YuocqA==", "requires": { - "@babel/polyfill": "7.4.4", - "@types/bluebird": "3.5.26", - "bluebird": "3.5.4", + "@babel/polyfill": "^7.4.3", + "@types/bluebird": "^3.5.26", + "bluebird": "^3.5.4", "colorette": "1.0.7", - "commander": "2.20.0", + "commander": "^2.20.0", "debug": "4.1.1", "getopts": "2.2.3", - "inherits": "2.0.3", - "interpret": "1.2.0", + "inherits": "~2.0.3", + "interpret": "^1.2.0", "liftoff": "3.1.0", - "lodash": "4.17.11", - "mkdirp": "0.5.1", + "lodash": "^4.17.11", + "mkdirp": "^0.5.1", "pg-connection-string": "2.0.0", - "tarn": "1.1.5", + "tarn": "^1.1.5", "tildify": "1.2.0", - "uuid": "3.3.2", - "v8flags": "3.1.2" + "uuid": "^3.3.2", + "v8flags": "^3.1.2" }, "dependencies": { "commander": { @@ -2267,7 +2273,7 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", "requires": { - "colornames": "1.1.1" + "colornames": "^1.1.1" } }, "lcid": { @@ -2276,7 +2282,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "2.0.0" + "invert-kv": "^2.0.0" } }, "levn": { @@ -2285,8 +2291,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "liftoff": { @@ -2294,14 +2300,14 @@ "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", "requires": { - "extend": "3.0.2", - "findup-sync": "3.0.0", - "fined": "1.2.0", - "flagged-respawn": "1.0.1", - "is-plain-object": "2.0.4", - "object.map": "1.0.1", - "rechoir": "0.6.2", - "resolve": "1.10.1" + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" } }, "load-json-file": { @@ -2310,10 +2316,10 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "4.1.15", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { @@ -2322,8 +2328,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lodash": { @@ -2342,11 +2348,11 @@ "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", "requires": { - "colors": "1.3.3", - "fast-safe-stringify": "2.0.6", - "fecha": "2.3.3", - "ms": "2.1.1", - "triple-beam": "1.3.0" + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" } }, "lolex": { @@ -2365,8 +2371,8 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "lru-queue": { @@ -2374,7 +2380,7 @@ "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "requires": { - "es5-ext": "0.10.50" + "es5-ext": "~0.10.2" } }, "make-dir": { @@ -2383,8 +2389,8 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "4.0.1", - "semver": "5.7.0" + "pify": "^4.0.1", + "semver": "^5.6.0" }, "dependencies": { "pify": { @@ -2400,7 +2406,7 @@ "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.2" } }, "map-age-cleaner": { @@ -2409,7 +2415,7 @@ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "p-defer": "1.0.0" + "p-defer": "^1.0.0" } }, "map-cache": { @@ -2422,7 +2428,7 @@ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "requires": { - "object-visit": "1.0.1" + "object-visit": "^1.0.0" } }, "mem": { @@ -2431,9 +2437,9 @@ "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "map-age-cleaner": "0.1.3", - "mimic-fn": "2.1.0", - "p-is-promise": "2.1.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" }, "dependencies": { "mimic-fn": { @@ -2449,14 +2455,14 @@ "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-weak-map": "2.0.2", - "event-emitter": "0.3.5", - "is-promise": "2.1.0", - "lru-queue": "0.1.0", - "next-tick": "1.0.0", - "timers-ext": "0.1.7" + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" } }, "merge-source-map": { @@ -2465,7 +2471,7 @@ "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "source-map": "0.6.1" + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -2481,19 +2487,19 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "mimic-fn": { @@ -2508,7 +2514,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -2521,8 +2527,8 @@ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -2530,7 +2536,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -2577,12 +2583,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "ms": { @@ -2597,7 +2603,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -2619,17 +2625,17 @@ "integrity": "sha512-976p3FxXdNMRRiF6Qe/FCOwaUYw3KXVJiIYu5iE5shM7ggIASgF6G/9gd9rhpBqP8V6MVa3KQJ6Ao1xBeGBljw==", "requires": { "cardinal": "1.0.0", - "denque": "1.4.1", - "generate-function": "2.3.1", - "iconv-lite": "0.4.24", - "long": "4.0.0", - "lru-cache": "4.1.5", + "denque": "^1.1.1", + "generate-function": "^2.0.0", + "iconv-lite": "^0.4.18", + "long": "^4.0.0", + "lru-cache": "^4.1.1", "named-placeholders": "1.1.1", - "object-assign": "4.1.1", + "object-assign": "^4.1.1", "readable-stream": "2.3.2", - "safe-buffer": "5.1.2", + "safe-buffer": "^5.0.1", "seq-queue": "0.0.5", - "sqlstring": "2.3.1" + "sqlstring": "^2.2.0" } }, "named-placeholders": { @@ -2652,17 +2658,17 @@ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" } }, "natural-compare": { @@ -2700,11 +2706,11 @@ "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", "dev": true, "requires": { - "@sinonjs/formatio": "3.2.1", - "@sinonjs/text-encoding": "0.7.1", - "just-extend": "4.0.2", - "lolex": "2.7.5", - "path-to-regexp": "1.7.0" + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" }, "dependencies": { "lolex": { @@ -2721,10 +2727,10 @@ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "hosted-git-info": "2.7.1", - "resolve": "1.10.1", - "semver": "5.7.0", - "validate-npm-package-license": "3.0.4" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "npm-run-path": { @@ -2733,7 +2739,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "number-is-nan": { @@ -2748,31 +2754,31 @@ "integrity": "sha512-iy9fEV8Emevz3z/AanIZsoGa8F4U2p0JKevZ/F0sk+/B2r9E6Qn+EPs0bpxEhnAt6UPlTL8mQZIaSJy8sK0ZFw==", "dev": true, "requires": { - "archy": "1.0.0", - "caching-transform": "3.0.2", - "convert-source-map": "1.6.0", - "cp-file": "6.2.0", - "find-cache-dir": "2.1.0", - "find-up": "3.0.0", - "foreground-child": "1.5.6", - "glob": "7.1.3", - "istanbul-lib-coverage": "2.0.5", - "istanbul-lib-hook": "2.0.7", - "istanbul-lib-instrument": "3.3.0", - "istanbul-lib-report": "2.0.8", - "istanbul-lib-source-maps": "3.0.6", - "istanbul-reports": "2.2.4", - "js-yaml": "3.13.1", - "make-dir": "2.1.0", - "merge-source-map": "1.1.0", - "resolve-from": "4.0.0", - "rimraf": "2.6.3", - "signal-exit": "3.0.2", - "spawn-wrap": "1.4.2", - "test-exclude": "5.2.3", - "uuid": "3.3.2", - "yargs": "13.2.2", - "yargs-parser": "13.1.0" + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" }, "dependencies": { "find-up": { @@ -2781,7 +2787,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { @@ -2790,8 +2796,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -2800,7 +2806,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -2809,7 +2815,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -2830,9 +2836,9 @@ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { "define-property": { @@ -2840,7 +2846,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "kind-of": { @@ -2848,7 +2854,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -2864,7 +2870,7 @@ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.0" } }, "object.assign": { @@ -2873,10 +2879,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "object-keys": "1.1.1" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, "object.defaults": { @@ -2884,10 +2890,10 @@ "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "requires": { - "array-each": "1.0.1", - "array-slice": "1.1.0", - "for-own": "1.0.0", - "isobject": "3.0.1" + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" } }, "object.entries": { @@ -2896,10 +2902,10 @@ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.13.0", - "function-bind": "1.1.1", - "has": "1.0.3" + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" } }, "object.map": { @@ -2907,8 +2913,8 @@ "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", "requires": { - "for-own": "1.0.0", - "make-iterator": "1.0.1" + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" } }, "object.pick": { @@ -2916,7 +2922,7 @@ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "once": { @@ -2925,7 +2931,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "one-time": { @@ -2939,7 +2945,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "optimist": { @@ -2948,8 +2954,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { "wordwrap": { @@ -2966,12 +2972,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "os-homedir": { @@ -2985,9 +2991,9 @@ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "1.0.0", - "lcid": "2.0.0", - "mem": "4.3.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "os-tmpdir": { @@ -3020,7 +3026,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -3029,7 +3035,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.3.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -3044,10 +3050,10 @@ "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", "dev": true, "requires": { - "graceful-fs": "4.1.15", - "hasha": "3.0.0", - "lodash.flattendeep": "4.4.0", - "release-zalgo": "1.0.0" + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" } }, "parent-module": { @@ -3056,7 +3062,7 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { - "callsites": "3.1.0" + "callsites": "^3.0.0" } }, "parse-filepath": { @@ -3064,9 +3070,9 @@ "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "requires": { - "is-absolute": "1.0.0", - "map-cache": "0.2.2", - "path-root": "0.1.1" + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" } }, "parse-json": { @@ -3075,7 +3081,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.2" + "error-ex": "^1.2.0" } }, "parse-passwd": { @@ -3122,7 +3128,7 @@ "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "requires": { - "path-root-regex": "0.1.2" + "path-root-regex": "^0.1.0" } }, "path-root-regex": { @@ -3153,7 +3159,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.0.0" } }, "pg-connection-string": { @@ -3173,7 +3179,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "2.1.0" + "find-up": "^2.1.0" } }, "posix-character-classes": { @@ -3209,8 +3215,8 @@ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "punycode": { @@ -3225,9 +3231,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.5.0", - "path-type": "2.0.0" + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" } }, "read-pkg-up": { @@ -3236,8 +3242,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" } }, "readable-stream": { @@ -3245,13 +3251,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.2.tgz", "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.2", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.0", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" } }, "rechoir": { @@ -3259,7 +3265,7 @@ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "requires": { - "resolve": "1.10.1" + "resolve": "^1.1.6" } }, "redeyed": { @@ -3267,7 +3273,7 @@ "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz", "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=", "requires": { - "esprima": "3.0.0" + "esprima": "~3.0.0" } }, "regenerator-runtime": { @@ -3280,8 +3286,8 @@ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, "regexpp": { @@ -3296,7 +3302,7 @@ "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { - "es6-error": "4.1.1" + "es6-error": "^4.0.1" } }, "repeat-element": { @@ -3326,7 +3332,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", "requires": { - "path-parse": "1.0.6" + "path-parse": "^1.0.6" } }, "resolve-dir": { @@ -3334,8 +3340,8 @@ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "requires": { - "expand-tilde": "2.0.2", - "global-modules": "1.0.0" + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" } }, "resolve-from": { @@ -3355,8 +3361,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "ret": { @@ -3370,7 +3376,7 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "7.1.3" + "glob": "^7.1.3" } }, "run-async": { @@ -3379,7 +3385,7 @@ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "rxjs": { @@ -3388,7 +3394,7 @@ "integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==", "dev": true, "requires": { - "tslib": "1.9.3" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -3401,7 +3407,7 @@ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { - "ret": "0.1.15" + "ret": "~0.1.10" } }, "safer-buffer": { @@ -3431,10 +3437,10 @@ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -3442,7 +3448,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -3453,7 +3459,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -3473,7 +3479,7 @@ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { - "is-arrayish": "0.3.2" + "is-arrayish": "^0.3.1" }, "dependencies": { "is-arrayish": { @@ -3489,13 +3495,13 @@ "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", "dev": true, "requires": { - "@sinonjs/commons": "1.4.0", - "@sinonjs/formatio": "3.2.1", - "@sinonjs/samsam": "3.3.1", - "diff": "3.5.0", - "lolex": "4.0.1", - "nise": "1.4.10", - "supports-color": "5.5.0" + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", + "diff": "^3.5.0", + "lolex": "^4.0.1", + "nise": "^1.4.10", + "supports-color": "^5.5.0" } }, "slice-ansi": { @@ -3504,9 +3510,9 @@ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "astral-regex": "1.0.0", - "is-fullwidth-code-point": "2.0.0" + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" } }, "snapdragon": { @@ -3514,14 +3520,14 @@ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.2", - "use": "3.1.1" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "dependencies": { "debug": { @@ -3537,7 +3543,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -3545,7 +3551,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "ms": { @@ -3560,9 +3566,9 @@ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "dependencies": { "define-property": { @@ -3570,7 +3576,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -3578,7 +3584,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -3586,7 +3592,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -3594,9 +3600,9 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -3606,7 +3612,7 @@ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.2.0" }, "dependencies": { "kind-of": { @@ -3614,7 +3620,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -3629,11 +3635,11 @@ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "requires": { - "atob": "2.1.2", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, "source-map-url": { @@ -3647,12 +3653,12 @@ "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", "dev": true, "requires": { - "foreground-child": "1.5.6", - "mkdirp": "0.5.1", - "os-homedir": "1.0.2", - "rimraf": "2.6.3", - "signal-exit": "3.0.2", - "which": "1.3.1" + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" } }, "spdx-correct": { @@ -3661,8 +3667,8 @@ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.4" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -3677,8 +3683,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.4" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -3692,7 +3698,7 @@ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "requires": { - "extend-shallow": "3.0.2" + "extend-shallow": "^3.0.0" } }, "sprintf-js": { @@ -3716,8 +3722,8 @@ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { "define-property": { @@ -3725,7 +3731,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -3736,8 +3742,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "string_decoder": { @@ -3745,7 +3751,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -3754,7 +3760,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { @@ -3781,7 +3787,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "table": { @@ -3790,10 +3796,10 @@ "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", "dev": true, "requires": { - "ajv": "6.10.0", - "lodash": "4.17.11", - "slice-ansi": "2.1.0", - "string-width": "3.1.0" + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { "ansi-regex": { @@ -3808,9 +3814,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "7.0.3", - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "5.2.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { @@ -3819,7 +3825,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -3835,10 +3841,10 @@ "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", "dev": true, "requires": { - "glob": "7.1.3", - "minimatch": "3.0.4", - "read-pkg-up": "4.0.0", - "require-main-filename": "2.0.0" + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" }, "dependencies": { "find-up": { @@ -3847,7 +3853,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "load-json-file": { @@ -3856,10 +3862,10 @@ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "graceful-fs": "4.1.15", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { @@ -3868,8 +3874,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -3878,7 +3884,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -3887,7 +3893,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -3902,8 +3908,8 @@ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "path-type": { @@ -3912,7 +3918,7 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "pify": { @@ -3927,9 +3933,9 @@ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.5.0", - "path-type": "3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, "read-pkg-up": { @@ -3938,8 +3944,8 @@ "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { - "find-up": "3.0.0", - "read-pkg": "3.0.0" + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } } } @@ -3966,7 +3972,7 @@ "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", "requires": { - "os-homedir": "1.0.2" + "os-homedir": "^1.0.0" } }, "timers-ext": { @@ -3974,8 +3980,8 @@ "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", "requires": { - "es5-ext": "0.10.50", - "next-tick": "1.0.0" + "es5-ext": "~0.10.46", + "next-tick": "1" } }, "tmp": { @@ -3984,7 +3990,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "to-fast-properties": { @@ -3998,7 +4004,7 @@ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -4006,7 +4012,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -4016,10 +4022,10 @@ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" } }, "to-regex-range": { @@ -4027,8 +4033,8 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } }, "trim-right": { @@ -4054,7 +4060,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -4070,8 +4076,8 @@ "dev": true, "optional": true, "requires": { - "commander": "2.20.0", - "source-map": "0.6.1" + "commander": "~2.20.0", + "source-map": "~0.6.1" }, "dependencies": { "commander": { @@ -4100,10 +4106,10 @@ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" }, "dependencies": { "extend-shallow": { @@ -4111,7 +4117,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "set-value": { @@ -4119,10 +4125,10 @@ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" } } } @@ -4132,8 +4138,8 @@ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { "has-value": { @@ -4141,9 +4147,9 @@ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" }, "dependencies": { "isobject": { @@ -4169,7 +4175,7 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "2.1.1" + "punycode": "^2.1.0" } }, "urix": { @@ -4197,7 +4203,7 @@ "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.2.tgz", "integrity": "sha512-MtivA7GF24yMPte9Rp/BWGCYQNaUj86zeYxV/x2RRJMKagImbbv3u8iJC57lNhWLPcGLJmHcHmFWkNsplbbLWw==", "requires": { - "homedir-polyfill": "1.0.3" + "homedir-polyfill": "^1.0.1" } }, "validate-npm-package-license": { @@ -4206,8 +4212,8 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "3.1.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "which": { @@ -4215,7 +4221,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -4229,15 +4235,15 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", "requires": { - "async": "2.6.2", - "diagnostics": "1.1.1", - "is-stream": "1.1.0", - "logform": "2.1.2", + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", "one-time": "0.0.4", - "readable-stream": "3.3.0", - "stack-trace": "0.0.10", - "triple-beam": "1.3.0", - "winston-transport": "4.3.0" + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" }, "dependencies": { "readable-stream": { @@ -4245,9 +4251,9 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", "requires": { - "inherits": "2.0.3", - "string_decoder": "1.2.0", - "util-deprecate": "1.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "string_decoder": { @@ -4255,7 +4261,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -4265,8 +4271,8 @@ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", "requires": { - "readable-stream": "2.3.6", - "triple-beam": "1.3.0" + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" }, "dependencies": { "process-nextick-args": { @@ -4279,13 +4285,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -4293,7 +4299,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -4310,8 +4316,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { @@ -4326,7 +4332,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { @@ -4335,9 +4341,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { @@ -4346,7 +4352,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -4363,7 +4369,7 @@ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "write-file-atomic": { @@ -4372,9 +4378,9 @@ "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", "dev": true, "requires": { - "graceful-fs": "4.1.15", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" } }, "y18n": { @@ -4394,17 +4400,17 @@ "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", "dev": true, "requires": { - "cliui": "4.1.0", - "find-up": "3.0.0", - "get-caller-file": "2.0.5", - "os-locale": "3.1.0", - "require-directory": "2.1.1", - "require-main-filename": "2.0.0", - "set-blocking": "2.0.0", - "string-width": "3.1.0", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "13.1.0" + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" }, "dependencies": { "ansi-regex": { @@ -4419,7 +4425,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { @@ -4428,8 +4434,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -4438,7 +4444,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -4447,7 +4453,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -4462,9 +4468,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "7.0.3", - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "5.2.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, "strip-ansi": { @@ -4473,7 +4479,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -4484,8 +4490,8 @@ "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } diff --git a/package.json b/package.json index c4d8266..8f1a58a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "mysql", + "name": "@janiscommerce/mysql", "version": "1.0.0", "description": "", "main": "index.js", @@ -20,6 +20,7 @@ "homepage": "https://github.com/janis-commerce/mysql#readme", "dependencies": { "@janiscommerce/logger": "^1.0.2", + "@janiscommerce/query-builder": "file ./../../query-builder", "knex": "^0.16.5", "mysql2": "1.5.2" }, diff --git a/query-builder/index.js b/query-builder/index.js deleted file mode 100644 index e89c436..0000000 --- a/query-builder/index.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const QueryBuilder = require('./query-builder'); -const QueryBuilderError = require('./query-builder-error'); - -module.exports = { - QueryBuilder, - QueryBuilderError -}; diff --git a/query-builder/query-builder-error.js b/query-builder/query-builder-error.js deleted file mode 100644 index 586e8cb..0000000 --- a/query-builder/query-builder-error.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -class QueryBuilderError extends Error { - - constructor(err) { - super(err); - this.message = err.message || err; - this.name = 'QueryBuilderError'; - } -} - -module.exports = QueryBuilderError; diff --git a/query-builder/query-builder.js b/query-builder/query-builder.js deleted file mode 100644 index a3efe86..0000000 --- a/query-builder/query-builder.js +++ /dev/null @@ -1,1108 +0,0 @@ -'use strict'; - -const logger = require('@janiscommerce/logger'); - -const Utils = require('./../utils'); - -const QueryBuilderError = require('./query-builder-error'); - -const JOIN_DEFAULT_METHOD = 'left'; -const JOIN_DEFAULT_OPERATOR = '='; - -class QueryBuilder { - - static get filterTypes() { - return { - equal: { multipleValues: true, multipleMethod: 'whereIn' }, // default - notEqual: { multipleValues: true, method: 'whereNot', multipleMethod: 'whereNotIn' }, - flagEqual: { method: 'whereRaw' }, - greater: { operator: '>' }, - greaterOrEqual: { operator: '>=' }, - lesser: { operator: '<' }, - lesserOrEqual: { operator: '<=' }, - search: { operator: 'LIKE', valuePrefix: '%', valueSuffix: '%' }, - between: { multipleValues: true, multipleMethod: 'whereBetween', needMultipleValues: 2 }, - notBetween: { multipleValues: true, multipleMethod: 'whereNotBetween', needMultipleValues: 2 }, - null: { method: 'whereNull', noValueNeeded: true }, - notNull: { method: 'whereNotNull', noValueNeeded: true } - }; - } - - static get selectFunctions() { - return { - count: 'count', - min: 'min', - max: 'max', - sum: 'sum', - avg: 'avg' - }; - } - - static get joinMethods() { - return { - join: 'join', - inner: 'innerJoin', - left: 'leftJoin', // default - leftOuter: 'leftOuter', - right: 'rightJoin', - rightOuter: 'rightOuterJoin', - fullOuter: 'fullOuterJoin', - cross: 'crossJoin' - }; - } - - static get joinOperators() { - return ['=', '!=']; - } - - constructor(knex, model, params) { - - this.knex = knex; - this.model = model; - - this.modelName = model.constructor.name; - - this.table = model.constructor.table; - this.fields = model.constructor.fields; - this.flags = model.constructor.flags; - this.joins = model.constructor.joins; - - this.params = params; - } - - /** - * Build query parameters - * - */ - build() { - - this._init(); - - this._buildSelect(); - - if(this._modelHasFieldsStructure()) { - this._buildJoins(); - this._buildFilters(); - this._buildOrder(); - this._buildGroup(); - } else - logger.warn('QueryBuilder - No fields structure'); - - this._buildLimit(); - - if(this.params.debug) - logger.info('statement', this.knexStatement.toString()); - } - - /** - * Initializes the Query Builder, creates the Knex Statement with table - * - */ - _init() { - - const { table = this.table } = this.params; - - this.knexStatement = this.knex({ t: this.model.addDbName(table) }); - this.knexStatement.raw = this.knex.raw; - } - - /** - * Indicates if Model has fields structure - * - * @return {boolean} true if has structure, false otherwise - */ - _modelHasFieldsStructure() { - return this.fields && Object.keys(this.fields).length > 0; - } - - /** - * Build Select. Call Knex Select with fields in params - * - */ - _buildSelect() { - - const { fields } = this.params; - - let fieldsForSelect; - let flagFieldsForSelect = false; - - if(typeof fields === 'undefined') { - fieldsForSelect = 't.*'; - - if(typeof this.params.noFlags === 'undefined' || !this.params.noFlags) - flagFieldsForSelect = this._getFlagFieldsForSelect(this._getFlagFields()); - - } else if(fields === false) - fieldsForSelect = false; - else if(Array.isArray(fields)) { - - this._validateFields(fields); - - fieldsForSelect = this._getFieldsForSelect(fields); - - flagFieldsForSelect = this._getFlagFieldsForSelect(fields); - - } else - throw new QueryBuilderError('Param \'fields\' must be an array'); - - let nothingToSelect = true; - - if(fieldsForSelect) { - this.knexStatement.select(fieldsForSelect); - nothingToSelect = false; - } - - if(flagFieldsForSelect) { - flagFieldsForSelect = Object.values(flagFieldsForSelect).join(', '); - this.knexStatement.select(this.knexStatement.raw(flagFieldsForSelect)); - nothingToSelect = false; - } - - const selectFunctions = this._getSelectFunctions(); - - for(const selectFunction of selectFunctions) { - this.knexStatement[selectFunction.method](selectFunction.select); - nothingToSelect = false; - } - - if(nothingToSelect) - throw new QueryBuilderError('Nothing to select'); - } - - /** - * Validate fields - * - * @param {array} fields The fields - * @return {boolean} true if valid, throw otherwise - */ - _validateFields(fields) { - - fields.forEach(field => { - if(!this._validateField(field)) - throw this.error; - }); - - return true; - } - - /** - * Validates a field - * - * @param {string} fieldName The field name - * @return {boolean} true if valid, false otherwise - */ - _validateField(fieldName) { - - if(!this._fieldExists(fieldName)) { - this.error = new QueryBuilderError(`Unknown field '${fieldName}', check ${this.modelName}.fields`); - return false; - } - - return true; - } - - /** - * Check if field exists - * - * @param {string} fieldName The field name - * @return {Boolean} true if exists, false otherwise - */ - _fieldExists(fieldName) { - return typeof this.fields[fieldName] !== 'undefined'; - } - - /** - * Gets the flag fields for the actual model. - * - * @return {Array} The flag fields. - */ - _getFlagFields() { - - if(typeof this.flags === 'undefined') - return []; - - let flagFields = []; - - for(const flags of Object.values(this.flags)) - flagFields = [...flagFields, ...Object.keys(flags)]; - - return flagFields; - } - - /** - * Filter and format fields for Select purpose - * - * @param {array} fields The fields - * @return {object|false} The fields for select, or false if empty - */ - _getFieldsForSelect(fields) { - - const fieldsForSelect = {}; - - fields.forEach(fieldName => { - - if(this._isFlagField(fieldName)) - return; - - fieldsForSelect[this._getAliasForSelect(fieldName)] = this._getFormattedField(fieldName); - }); - - return Object.keys(fieldsForSelect).length ? fieldsForSelect : false; - } - - /** - * Determines if the flag is a 'flag field'. - * - * @param {string} fieldName The field name - * @return {boolean} True if flag field, False otherwise. - */ - _isFlagField(fieldName) { - - const flagData = this._getFlagData(fieldName); - - return flagData !== false; - } - - - /** - * Gets the flag data. - * - * @param {string} fieldName The field name - * @return {(Object|false)} The flag data or false if not a flag. - */ - _getFlagData(fieldName) { - - if(typeof this.flags === 'undefined') - return false; - - for(const [field, flags] of Object.entries(this.flags)) { - for(const [flag, flagValue] of Object.entries(flags)) { - if(fieldName !== flag) - continue; - - if(!this._validateField(field)) - throw new QueryBuilderError(this.error); - - return { field, value: flagValue }; - } - } - - return false; - } - - /** - * Gets the alias for select. - * - * @param {string} fieldName The field name - * @return {string} The alias for select. - */ - _getAliasForSelect(fieldName) { - return typeof this.fields[fieldName] === 'object' && this.fields[fieldName].alias ? this.fields[fieldName].alias : fieldName; - } - - /** - * Gets the field correct string to select, filter or order - * - * @param {string} fieldName The field name - * @return {string} The formatted field. - */ - _getFormattedField(fieldName) { - - if(this._isFlagField(fieldName)) - return this._getFormattedFlagField(fieldName); - - return `${this._getTableAliasFromField(fieldName)}.${this._getRealFieldName(fieldName)}`; - } - - /** - * Gets the table alias for field. - * - * @param {string} fieldName The field name - * @return {string} The table alias. - */ - _getTableAliasFromField(fieldName) { - - if(typeof this.fields[fieldName] !== 'object') - return 't'; - - const field = this.fields[fieldName]; - - if(field.table && this.joins && this.joins[field.table] && this.joins[field.table].alias) - return this.joins[field.table].alias; - - return 't'; - } - - /** - * Gets the real field name. - * - * @param {string} fieldName The field name - * @return {string} The real field name. - */ - _getRealFieldName(fieldName) { - - const field = this.fields[fieldName]; - - if(typeof field === 'boolean') - return fieldName; - - if(typeof field === 'string') - return field; - - if(typeof field === 'object' && field.field && typeof field.field === 'string') - return field.field; - - return fieldName; - } - - _getFlagFieldsForSelect(fields) { - - const flagFieldsForSelect = {}; - - fields.forEach(fieldName => { - - if(!this._isFlagField(fieldName)) - return; - - const { value } = this._getFlagData(fieldName); - - flagFieldsForSelect[fieldName] = `(${this._getFormattedFlagField(fieldName)} = ${value}) as ${fieldName}`; - }); - - return Object.keys(flagFieldsForSelect).length ? flagFieldsForSelect : false; - } - - _getFormattedFlagField(fieldName) { - - const { field, value } = this._getFlagData(fieldName); - - return `(${this._getTableAliasFromField(field)}.${this._getRealFieldName(field)} & ${value})`; - } - - /** - * Gets the select functions. - * - * @return {(Object|boolean)} The select functions. - */ - _getSelectFunctions() { - - const selectFunctions = []; - - for(const [selectFunction, selectMethod] of Object.entries(this.constructor.selectFunctions)) { - - if(!this.params[selectFunction]) - continue; - - const data = this.params[selectFunction]; - - if(Array.isArray(data)) - throw new QueryBuilderError(`Param '${selectFunction}' can't be an array`); - - let field = '*'; - let alias = selectFunction; - - if(data === true) - field = '*'; - else if(typeof data === 'string') - field = data; - else if(typeof data === 'object') { - - if(data.field) - ({ field } = data); - - if(data.alias) - ({ alias } = data); - } else - throw new QueryBuilderError(`Param '${selectFunction}' invalid format`); - - if(field !== '*') { - if(!this._validateField(field)) - throw this.error; - - field = this._getFormattedField(field); - } - - selectFunctions.push({ - method: selectMethod, - select: `${field} as ${alias}` - }); - } - - return selectFunctions; - } - - /** - * Build Joins. - */ - _buildJoins() { - - const { joins } = this.params; - - if(!joins) - return; - - if(!Array.isArray(joins)) - throw new QueryBuilderError('Param \'joins\' must be an array'); - - joins.forEach(joinKey => this._makeJoin(joinKey)); - } - - /** - * Add a single join. - * - * @param {string} joinKey The join key - */ - _makeJoin(joinKey) { - - if(!this._validateJoin(joinKey)) - throw this.error; - - const joinTable = this._getJoinTable(joinKey); - const joinMethod = this._getJoinMethod(joinKey); - - this.knexStatement[joinMethod](joinTable, builder => { - - const modelJoin = this.joins[joinKey]; - - if(modelJoin.multipleOn) { - - const onMethod = modelJoin.on ? 'on' : 'orOn'; - - modelJoin[onMethod].forEach(onItem => { - const joinFields = this._getFormattedJoinFields(onItem); - builder[onMethod](...joinFields); - }); - } else { - const joinFields = this._getFormattedJoinFields(modelJoin.on); - builder.on(...joinFields); - } - }); - } - - - /** - * Validates the join structure - * - * @param {string} joinKey The join key - * @return {boolean} true if valid, false otherwise - */ - _validateJoin(joinKey) { - - if(!this._joinExists(joinKey)) { - this.error = new QueryBuilderError(`Unknown joinKey '${joinKey}', check ${this.modelName}.joins`); - return false; - } - - const modelJoin = this.joins[joinKey]; - - if(typeof modelJoin !== 'object') { - this.error = new QueryBuilderError(`join '${joinKey}' must be an object, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - if(!modelJoin.alias) { - this.error = new QueryBuilderError(`join '${joinKey}' 'alias' is required, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - if(modelJoin.method && !this._joinMethodExists(modelJoin.method)) { - this.error = new QueryBuilderError(`invalid join method '${modelJoin.method}', check QueryBuilder.joinMethods`); - return false; - } - - if(!modelJoin.on && !modelJoin.orOn) { - this.error = new QueryBuilderError(`join '${joinKey}' 'on' or 'orOn' are required, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - if(modelJoin.on) - return this._validateJoinOn(joinKey); - - return this._validateJoinOn(joinKey, 'orOn'); - } - - /** - * Indicates if join exists - * - * @param {string} joinKey The join key - * @return {Boolean} true if exists, false otherwise - */ - _joinExists(joinKey) { - return typeof this.joins[joinKey] !== 'undefined'; - } - - /** - * Indicates if join method exists - * - * @param {string} joinMethod The join method - * @return {Boolean} true if exists, false otherwise - */ - _joinMethodExists(joinMethod) { - return typeof this.constructor.joinMethods[joinMethod] !== 'undefined'; - } - - /** - * Indicates if operator is valid - * - * @param {string} operator The join operator - * @return {boolear} true if valid operator, false otherwise - */ - _validateJoinOperator(operator) { - - if(!this.constructor.joinOperators.includes(operator)) { - this.error = new QueryBuilderError(`Unknown join operator '${operator}'`); - return false; - } - - return true; - } - - /** - * Validates Joins 'on' part - * - * @param {string} joinKey The join key - * @param {string} type The type - * @return {boolean} true if valid, false otherwise - */ - _validateJoinOn(joinKey, type = 'on') { - - const on = this.joins[joinKey][type]; - - if(!Array.isArray(on)) { - this.error = new QueryBuilderError(`join '${joinKey}' parts 'on' and 'orOn' must be an array, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - if(!on.length) { - /* eslint-disable max-len */ - this.error = new QueryBuilderError(`join '${joinKey}' parts 'on' and 'orOn' must be an array with content, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - if(on.every(joinField => typeof joinField === 'string')) - return this._validateJoinFields(joinKey, on); - - if(on.every(joinFields => Array.isArray(joinFields))) { - this.joins[joinKey].multipleOn = true; - return on.every(joinFields => this._validateJoinFields(joinKey, joinFields)); - } - - /* eslint-disable max-len */ - this.error = new QueryBuilderError(`join '${joinKey}' parts 'on' and 'orOn' must be an arrays or strings, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - /** - * Validates Joins 'fields' part - * - * @param {string} joinKey The join key - * @param {array} joinFields the join fields - * @return {boolean} true if valid fields, false otherwise - */ - _validateJoinFields(joinKey, joinFields) { - - if(joinFields.length !== 2 && joinFields.length !== 3) { - this.error = new QueryBuilderError(`join '${joinKey}' parts 'on' and 'orOn' must have 2 o 3 values, check ${this.modelName}.joins.${joinKey}`); - return false; - } - - let operator = JOIN_DEFAULT_OPERATOR; - let fieldA; - let fieldB; - - if(joinFields.length === 2) - [fieldA, fieldB] = joinFields; - else - [fieldA, operator, fieldB] = joinFields; - - return this._validateField(fieldA) - && !this._isFlagField(fieldA) - && this._validateJoinOperator(operator) - && this._validateField(fieldB) - && !this._isFlagField(fieldB); - } - - /** - * Gets the join method. - * - * @param {string} joinKey The join key - * @return {string} The join method. - */ - _getJoinMethod(joinKey) { - const joinMethod = this.joins[joinKey].method || JOIN_DEFAULT_METHOD; - return this.constructor.joinMethods[joinMethod]; - } - - /** - * Gets the join table. - * - * @param {string} joinKey The join key - * @return {string} The join table. - */ - _getJoinTable(joinKey) { - const modelJoin = this.joins[joinKey]; - const table = modelJoin.table || joinKey; - return `${this.model.addDbName(table)} as ${modelJoin.alias}`; - } - - /** - * Gets the formatted join fields. - * - * @param {array} joinFields The join fields - * @return {array} The formatted join fields. - */ - _getFormattedJoinFields(joinFields) { - return joinFields.map(joinField => { - return this._fieldExists(joinField) ? this._getFormattedField(joinField) : joinField; - }); - } - - /** - * Builds filters. Add AND or OR filters. - */ - _buildFilters() { - - const { filters } = this.params; - - if(!filters) - return; - - if(Array.isArray(filters)) - filters.forEach(filterItems => this._addOrFilters(filterItems)); - else - this._addAndFilters(filters); - } - - /** - * Adds OR filters. Calling Knex orWhere method - * - * @param {object} filters The filters - */ - _addOrFilters(filters) { - this.knexStatement.orWhere(builder => this._makeFilters(builder, filters)); - } - - /** - * Adds AND filters. Calling Knex where method - * - * @param {object} filters The filters - */ - _addAndFilters(filters) { - this.knexStatement.where(builder => this._makeFilters(builder, filters)); - } - - /** - * Makes filters. Calling Knex where methods - * - * @param {function} builder The Knex builder callback - * @param {object} filters The filters - */ - _makeFilters(builder, filters) { - - for(const [fieldName, filter] of Object.entries(filters)) { - - if(!this._validateFilter(fieldName, filter)) - throw this.error; - - const filterMethod = this._getFilterMethod(fieldName, filter); - - const filterParams = this._getFilterParams(fieldName, filter); - - builder[filterMethod](...filterParams); - } - } - - /** - * Check if filter is valid - * - * @param {string} fieldName The field name - * @param {mixed} filter The filter content, value or object with definition - * @return {boolean} True if filter is valid, false otherwise - */ - _validateFilter(fieldName, filter) { - - if(!this._validateField(fieldName)) - return false; - - const filterType = this._getFilterType(fieldName, filter); - - if(!this._filterTypeExists(filterType)) { - this.error = new QueryBuilderError(`Unknown filter type '${filterType}' for filter '${fieldName}'`); - return false; - } - - const isMultiple = this._filterIsMultiple(fieldName, filter); - - if(isMultiple && !this._filterTypeAllowsMultipleValues(filterType)) { - this.error = new QueryBuilderError(`Filter type '${filterType}' not allows multiple values for filter '${fieldName}'`); - return false; - } - - const { needMultipleValues } = this.constructor.filterTypes[filterType]; - - if(needMultipleValues) { - - // needMultipleValues es la cantidad de valores que "necesita" para filtrar - - if(!isMultiple) { - this.error = new QueryBuilderError(`Filter type '${filterType}' needs multiple values for filter '${fieldName}'`); - return false; - } - - const filterValue = this._getFilterValue(fieldName, filter); - - if(filterValue.length !== needMultipleValues) { - this.error = new QueryBuilderError(`Filter type '${filterType}' must an array with 2 values for filter '${fieldName}'`); - return false; - } - } - - return true; - } - - /** - * Gets the filter type. - * - * @param {string} fieldName The field name - * @param {object} filter The filter data - * @return {string} The filter type. - */ - _getFilterType(fieldName, filter) { - - if(this._isFlagField(fieldName)) - return 'flagEqual'; - - if(typeof filter === 'object' && filter.type) - return filter.type; - - if(typeof this.fields[fieldName] === 'object' && this.fields[fieldName].type) - return this.fields[fieldName].type; - - return 'equal'; - } - - /** - * Check if filter type exists - * - * @param {string} filterType The filter type - * @return {boolean} True if filter exists, false otherwise - */ - _filterTypeExists(filterType) { - return filterType && typeof filterType === 'string' && this.constructor.filterTypes[filterType]; - } - - /** - * Check if filter value is multiple - * - * @param {string} fieldName The field name - * @param {mixed} filter The filter content or value - * @return {boolean} true if is multiple, false otherwise - */ - _filterIsMultiple(fieldName, filter) { - return Array.isArray(this._getFilterValue(fieldName, filter)); - } - - /** - * Gets the filter value. - * - * @param {string} fieldName The field name - * @param {mixed} filter The filter content or value - * @return {mixed} The filter value. - */ - _getFilterValue(fieldName, filter) { - - let value = filter.value || filter; - - if(this._isFlagField(fieldName)) - value = value ? this._getFlagData(fieldName).value : 0; - - return value; - } - - /** - * Prepares filter value - * - * @param {string} fieldName The field name - * @param {mixed} filter The filter content or value - * @return {mixed} filter value - */ - _prepareFilterValue(fieldName, filter) { - - const filterType = this._getFilterType(fieldName, filter); - const valuePrefix = this._getFilterValuePrefix(filterType); - const valueSuffix = this._getFilterValueSuffix(filterType); - - const addPrefixAndSuffix = value => { - if(valuePrefix) - value = `${valuePrefix}${value}`; - - if(valueSuffix) - value = `${value}${valueSuffix}`; - - return value; - }; - - const filterValue = this._getFilterValue(fieldName, filter); - - return this._filterIsMultiple(fieldName, filter) ? filterValue.map(addPrefixAndSuffix) : addPrefixAndSuffix(filterValue); - } - - /** - * Check if filter allows multiple values - * - * @param {string} filterType The filter type - * @return {boolean} True if filter allows multiple values, false otherwise - */ - _filterTypeAllowsMultipleValues(filterType) { - return this._filterTypeExists(filterType) && this.constructor.filterTypes[filterType].multipleValues; - } - - /** - * Check if should add filter value from filter type - * - * @param {string} filterType The filter type - * @return {boolean} True if should add filter value, false otherwise - */ - _filterNeedsValue(filterType) { - return this._filterTypeExists(filterType) && !this.constructor.filterTypes[filterType].noValueNeeded; - } - - /** - * Gets the filter value prefix. - * - * @param {string} filterType The filter type - * @return {string|boolean} The filter value prefix if any, false otherwise - */ - _getFilterValuePrefix(filterType) { - return this._filterTypeExists(filterType) && this.constructor.filterTypes[filterType].valuePrefix ? this.constructor.filterTypes[filterType].valuePrefix : false; - } - - /** - * Gets the filter value suffix. - * - * @param {string} filterType The filter type - * @return {string|boolean} The filter value suffix if any, false otherwise - */ - _getFilterValueSuffix(filterType) { - return this._filterTypeExists(filterType) && this.constructor.filterTypes[filterType].valueSuffix ? this.constructor.filterTypes[filterType].valueSuffix : false; - } - - /** - * Gets the Knex filter method from a filter type. - * - * @param {string} fieldName The field name - * @param {object} filter The filter data - * @return {string} The filter Knex method name. - */ - _getFilterMethod(fieldName, filter) { - - const filterType = this._getFilterType(fieldName, filter); - - const isMultiple = this._filterIsMultiple(fieldName, filter); - - if(isMultiple) - return this.constructor.filterTypes[filterType].multipleMethod; - - return this.constructor.filterTypes[filterType].method || 'where'; - } - - /** - * Gets the filter parameters. Formatted field, operator if necesary and value if necesary - * - * @param {string} fieldName The field name - * @param {mixed} filter The filter data - * @return {Array} The filter parameters. - */ - _getFilterParams(fieldName, filter) { - - const filterParams = [ - this._getFormattedField(fieldName) - ]; - - if(this._isFlagField(fieldName)) - filterParams[0] += ' = ?'; - - const filterType = this._getFilterType(fieldName, filter); - - const filterOperator = this._getFilterOperator(filterType); - - if(filterOperator) - filterParams.push(filterOperator); - - if(this._filterNeedsValue(filterType)) - filterParams.push(this._prepareFilterValue(fieldName, filter)); - - return filterParams; - } - - /** - * Gets the filter operator if any - * - * @param {string} filterType The filter type - * @return {(boolean|string)} The filter operator is any, false otherwise - */ - _getFilterOperator(filterType) { - return this.constructor.filterTypes[filterType].operator || false; - } - - /** - * Build Order By. Call Knex orderBy with order in params - * - */ - _buildOrder() { - - let { order } = this.params; - - if(!order) - return; - - if(typeof order === 'string') - order = { [order]: 'asc' }; - - if(Array.isArray(order)) - throw new QueryBuilderError('Param order must be an object or string'); - - for(const [fieldName, direction] of Object.entries(order)) { - - if(!this._validateOrderItem(fieldName, direction)) - throw this.error; - - const formattedField = this._getFormattedField(fieldName); - - if(this._isFlagField(fieldName)) - this.knexStatement.orderByRaw(`${formattedField} ${direction}`); - else - this.knexStatement.orderBy(formattedField, direction); - } - } - - /** - * Validates an order item - * - * @param {string} fieldName The field name - * @param {string} direction The order by direction (asc, desc) - * @return {boolean} true if valid, false otherwise - */ - _validateOrderItem(fieldName, direction) { - - if(!this._validateField(fieldName)) - return false; - - if(direction !== 'asc' && direction !== 'desc') { - this.error = new QueryBuilderError(`Order By direccion must be 'asc' or 'desc', direction received '${direction}'`); - return false; - } - - return true; - } - - /** - * Builds group by function. - * - */ - _buildGroup() { - - let { group } = this.params; - - if(typeof group === 'undefined' || group === false) - return; - - if(typeof group === 'string') - group = [group]; - else if(!Array.isArray(group)) - throw new QueryBuilderError('Param \'group\' must be string or an array'); - - if(!group.length) - throw new QueryBuilderError(`Param 'group' must have some fields, check ${this.modelName}.fields`); - - group = Utils.arrayUnique(group); - - group.forEach(fieldName => { - - if(!this._validateGroupItem(fieldName)) - throw this.error; - - const formattedField = this._getFormattedField(fieldName); - - if(this._isFlagField(fieldName)) - this.knexStatement.groupByRaw(formattedField); - else - this.knexStatement.groupBy(formattedField); - }); - } - - /** - * Validates a group item - * - * @param {string} fieldName The field name - * @return {boolean} true if valid, false otherwise - */ - _validateGroupItem(fieldName) { - - if(!this._validateField(fieldName)) - return false; - - return true; - } - - /** - * Build Limit and Offset. Call Knex limit and offset methods with params - * - */ - _buildLimit() { - - let { limit } = this.params; - - if(limit) { - - limit = Number(limit); - - if(!Number.isNaN(limit) || Number.isInteger(limit)) - this.knexStatement.limit(limit); - else - throw new QueryBuilderError('Invalid \'limit\' format, \'limit\' must be an integer'); - } - - let { page } = this.params; - - if(page) { - - page = Number(page); - - if(Number.isNaN(page) || !Number.isInteger(page)) - throw new QueryBuilderError('Invalid \'page\' format, \'page\' must be an integer'); - - if(!limit) - throw new QueryBuilderError('Param \'limit\' is required when param page given'); - - const offset = (page - 1) * limit; - - this.knexStatement.offset(offset); - } else { - - let { offset } = this.params; - - if(!offset) - return; - - offset = Number(offset); - - if(Number.isNaN(offset) || !Number.isInteger(offset)) - throw new QueryBuilderError('Invalid \'offset\' format, \'offset\' must be an integer'); - - this.knexStatement.offset(offset); - } - } - - /** - * Executes the Knex Statement - * - * @return {Promise} The promise - */ - async execute() { - return this.knexStatement; - } - -} - -module.exports = QueryBuilder; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index a9ae3cc..d8b8643 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -3,9 +3,10 @@ const assert = require('assert'); const sinon = require('sinon'); -const { MySQL, MySQLError } = require('./../mysql'); +const QueryBuilder = require('@janiscommerce/query-builder'); -const { QueryBuilder } = require('./../query-builder'); +const { MySQLError } = require('./../mysql'); +const MySQL = require('./../index'); /* eslint-disable prefer-arrow-callback */ diff --git a/tests/query-builder-test.js b/tests/query-builder-test.js deleted file mode 100644 index 8787cea..0000000 --- a/tests/query-builder-test.js +++ /dev/null @@ -1,1548 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const sinon = require('sinon'); - -const { QueryBuilder, QueryBuilderError } = require('./../query-builder'); - -/* eslint-disable prefer-arrow-callback */ - -const makeKnex = () => { - class FakeKnex {} - - const knexMethods = [ - 'select', 'raw', 'count', 'min', 'max', 'sum', 'avg', - 'where', 'orWhere', 'whereIn', 'whereNotIn', 'whereNot', 'whereNull', 'whereNotNull', 'whereBetween', 'whereNotBetween', 'whereRaw', - 'join', 'innerJoin', 'leftJoin', 'leftOuter', 'rightJoin', 'rightOuterJoin', 'fullOuterJoin', 'crossJoin', - 'on', 'orOn', - 'groupBy', 'groupByRaw', - 'limit', 'offset', - 'orderBy', 'orderByRaw' - ]; - - knexMethods.forEach(knexMethod => { FakeKnex[knexMethod] = sinon.stub(); }); - - return FakeKnex; -}; - -const makeKnexFunction = () => makeKnex; - -function queryBuilderFactory({ - params = {}, - table = 'table', - fields = {}, - flags = {}, - joins = {}, - knexSpy -} = {}) { - - class Model { - static get table() { - return table; - } - - get dbTable() { - return table; - } - - static get fields() { - return fields; - } - - static get flags() { - // el if es para poder testear los casos en los que los modelos NO tienen flags - return Object.keys(flags).length > 0 ? flags : undefined; - } - - static get joins() { - return joins; - } - - addDbName(t) { - return t; - } - } - - const knex = knexSpy || makeKnexFunction(); - knex.raw = sinon.stub(); - const model = new Model(); - - return new QueryBuilder(knex, model, params); -} - -const assertThrowsWhenBuild = queryBuilder => assert.throws(() => { queryBuilder.build(); }, QueryBuilderError); - -describe('QueryBuilder', function() { - - describe('constructor -', function() { - - it('Should construct a QueryBuilder with base params', function() { - - const params = { dummyParams: 1 }; - - const fakeTable = 'fake_table'; - - const queryBuilder = queryBuilderFactory({ - params, - table: fakeTable - }); - - assert.equal(queryBuilder.table, fakeTable); - assert.deepEqual(queryBuilder.fields, {}); - assert.deepEqual(queryBuilder.params, params); - }); - }); - - describe('build', function() { - - it('Should init knex with table \'foo\'', function() { - - const knexSpy = sinon.stub(); - knexSpy.returns({ select() {} }); - - const queryBuilder = queryBuilderFactory({ table: 'foo', knexSpy }); - - queryBuilder.build(); - - assert(knexSpy.calledOnce); - assert.deepEqual(knexSpy.args[0][0], { t: 'foo' }); - }); - - it('Should build normaly when debug mode on', function() { - const queryBuilder = queryBuilderFactory({ params: { debug: true } }); - queryBuilder.build(); - }); - }); - - describe('buildSelect', function() { - - describe('Should throws', function() { - it('when invalid params.fields passed and fields definition missed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true }, - params: { fields: 'id' } - }); - - assertThrowsWhenBuild(queryBuilder); - - assert(queryBuilder.knexStatement.select.notCalled); - }); - - it('when \'params.fields\' not present in fields definition missed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true }, - params: { fields: ['bar'] } - }); - - assertThrowsWhenBuild(queryBuilder); - - assert(queryBuilder.knexStatement.select.notCalled); - }); - - it('when params.fields passed and fields definition missed', function() { - - const queryBuilder = queryBuilderFactory({ params: { fields: ['id'] } }); - - assertThrowsWhenBuild(queryBuilder); - - assert(!queryBuilder.knexStatement.select.called); - }); - - it('when params.fields as an empty array', function() { - - const queryBuilder = queryBuilderFactory({ params: { fields: [] } }); - - assertThrowsWhenBuild(queryBuilder); - - assert(!queryBuilder.knexStatement.select.called); - }); - - it('when params.fields passed as false and no select functions', function() { - - const queryBuilder = queryBuilderFactory({ params: { fields: false } }); - - assertThrowsWhenBuild(queryBuilder); - - assert(!queryBuilder.knexStatement.select.called); - }); - }); - - describe('Should call knex.select()', function() { - - it('when params.fields passed and definition exists', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true }, - params: { fields: ['id'] } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.called); - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], { id: 't.id' }); - }); - - it('when params.fields passed and complex with \'field\' definition exists', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: { field: 'bar' } }, - params: { fields: ['foo'] } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.called); - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], { foo: 't.bar' }); - }); - - it('when params.fields passed and complex with \'field\' and \'alias\' definition exists', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: { field: 'bar', alias: 'greatAlias' } }, - params: { fields: ['foo'] } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.called); - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], { greatAlias: 't.bar' }); - }); - - it('when no params.fields passed and fields definition missed', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.calledOnce); - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], 't.*'); - }); - - it('when fields definition exists but no params.fields passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.calledOnce); - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], 't.*'); - }); - - it('with t.* and model flags when no params.fields given', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true, status: true, isActive: true, error: true }, - flags: { status: { isActive: 1, error: 2 } } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.calledTwice); - - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], 't.*'); - - assert(queryBuilder.knexStatement.raw.calledOnce); - - assert.deepEqual(queryBuilder.knexStatement.raw.args[0][0], '((t.status & 1) = 1) as isActive, ((t.status & 2) = 2) as error'); - }); - - it('with t.* and no flags when no fields given and noFlags param as true', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true, status: true, isActive: true, error: true }, - flags: { status: { isActive: 1, error: 2 } }, - params: { noFlags: true } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.calledOnce); - - assert.deepEqual(queryBuilder.knexStatement.select.args[0][0], 't.*'); - - assert(queryBuilder.knexStatement.raw.notCalled); - }); - }); - - describe('Shouldn\'t call knex.select()', function() { - - it('when params.fields passed as false and has select functions', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true }, - params: { fields: false, count: true } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.notCalled); - }); - }); - }); - - describe('buildSelect - Select Functions', function() { - - describe('Shouldn\'t call knex.count()', function() { - - it('when no params.count passed', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.count.notCalled); - }); - }); - - describe('Should throws', function() { - - it('when params.count passed with unknown field', function() { - - const queryBuilder = queryBuilderFactory({ - params: { count: 'unknown' } - }); - - assertThrowsWhenBuild(queryBuilder); - - assert(queryBuilder.knexStatement.count.notCalled); - }); - - it('when invalid formatted params.count passed', function() { - - const invalidCounts = [ - 10, - [], - [1], - ['foo'], - ['foo', 'bar'], - ['foo', 'bar', 10] - ]; - - invalidCounts.forEach(invalidCount => { - - const queryBuilder = queryBuilderFactory({ - params: { count: invalidCount } - }); - - assertThrowsWhenBuild(queryBuilder); - - assert(queryBuilder.knexStatement.count.notCalled); - }); - }); - - }); - - const selectFunctions = ['count', 'min', 'max', 'sum', 'avg']; - - selectFunctions.forEach(selectFunction => { - - describe(`Should call knex.${selectFunction} method`, function() { - - it(`when ${selectFunction} as 'true' passed`, function() { - - const queryBuilder = queryBuilderFactory({ - params: { [selectFunction]: true } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement[selectFunction].calledOnce); - assert.deepEqual(queryBuilder.knexStatement[selectFunction].args[0], [`* as ${selectFunction}`]); - - }); - - it(`when ${selectFunction} as valid field passed`, function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true }, - params: { [selectFunction]: 'id' } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement[selectFunction].calledOnce); - assert.deepEqual(queryBuilder.knexStatement[selectFunction].args[0], [`t.id as ${selectFunction}`]); - - }); - - it(`when ${selectFunction} as object with valid field passed`, function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true }, - params: { [selectFunction]: { field: 'id' } } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement[selectFunction].calledOnce); - assert.deepEqual(queryBuilder.knexStatement[selectFunction].args[0], [`t.id as ${selectFunction}`]); - - }); - - it(`when ${selectFunction} as object with alias passed`, function() { - - const queryBuilder = queryBuilderFactory({ - params: { [selectFunction]: { alias: `${selectFunction}Alias` } } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement[selectFunction].calledOnce); - assert.deepEqual(queryBuilder.knexStatement[selectFunction].args[0], [`* as ${selectFunction}Alias`]); - - }); - - it(`when ${selectFunction} as object with valid field and alias passed`, function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: true }, - params: { [selectFunction]: { field: 'id', alias: `${selectFunction}Alias` } } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement[selectFunction].calledOnce); - assert.deepEqual(queryBuilder.knexStatement[selectFunction].args[0], [`t.id as ${selectFunction}Alias`]); - - }); - }); - }); - }); - - describe('buildSelect - Select Flags', function() { - - describe('Should call knex.select() and knex.raw()', function() { - - it('when params.fields with a flag passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { - status: true, - isActive: true - }, - flags: { status: { isActive: 1 } }, - params: { fields: ['isActive'] } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.called); - assert(queryBuilder.knexStatement.raw.called); - - assert.deepEqual(queryBuilder.knexStatement.raw.args[0][0], '((t.status & 1) = 1) as isActive'); - }); - }); - }); - - describe('buildJoins', function() { - - describe('Shouldn\'t call knex.join methods', function() { - - const joinKnexMethods = ['join', 'innerJoin', 'leftJoin', 'leftOuter', 'rightJoin', 'rightOuterJoin', 'fullOuterJoin', 'crossJoin']; - - const assertShouldntJoin = queryBuilderParams => { - const queryBuilder = queryBuilderFactory(queryBuilderParams); - - queryBuilder.build(); - - joinKnexMethods.forEach(method => { - assert(queryBuilder.knexStatement[method].notCalled); - }); - }; - - it('when no \'params.joins\' passed or fields and join definition missing', function() { - assertShouldntJoin(); - }); - - it('when \'params.joins\' passed but fields and join definition missing', function() { - - assertShouldntJoin({ - params: { joins: ['foo'] } - }); - }); - - it('when \'params.joins\' passed but fields definition missing', function() { - - assertShouldntJoin({ - params: { joins: ['foo'] }, - joins: { foo: { alias: 'f', on: ['fieldA', 'fieldB'] } } - }); - }); - }); - - describe('Should throws', function() { - - const assertThrowsBuildJoins = queryBuilderParams => { - const queryBuilder = queryBuilderFactory(queryBuilderParams); - assert.throws(() => { queryBuilder.build(); }, QueryBuilderError, JSON.stringify(queryBuilderParams)); - }; - - it('when invalid \'params.joins\' passed', function() { - - assertThrowsBuildJoins({ - params: { joins: 'foo' }, - fields: { foo: true } - }); - }); - - it('when \'params.joins\' passed but join definition missing', function() { - - assertThrowsBuildJoins({ - params: { joins: ['foo'] }, - fields: { foo: true } - }); - }); - - it('when \'params.joins\' and join definition join key does not match', function() { - - // testing QueryBuilder._validateJoin - - assertThrowsBuildJoins({ - params: { joins: ['foo'] }, - fields: { foo: true }, - joins: { bar: { alias: 'b', on: ['fieldA', 'fieldB'] } } - }); - }); - - it('when join definition is invalid', function() { - - // testing QueryBuilder._validateJoin - - assertThrowsBuildJoins({ - params: { joins: ['joinA', 'joinA', 'joinC', 'joinD'] }, - fields: { foo: true }, - joins: { - joinA: 'some wrong definition', - joinB: true, - joinC: 80, - joinD: ['foo', 'bar'] - } - }); - }); - - it('when \'alias\' is missing in join definition', function() { - - // testing QueryBuilder._validateJoin - - assertThrowsBuildJoins({ - params: { joins: ['joinA'] }, - fields: { foo: true }, - joins: { joinA: {} } - }); - }); - - it('when \'method\' is missing in join definition', function() { - - // testing QueryBuilder._validateJoin - - assertThrowsBuildJoins({ - params: { joins: ['joinA'] }, - fields: { foo: true }, - joins: { joinA: { alias: 'j' } } - }); - }); - - it('when \'method\' is invalid in join definition', function() { - - // testing QueryBuilder._validateJoin - - assertThrowsBuildJoins({ - params: { joins: ['joinA'] }, - fields: { foo: true }, - joins: { joinA: { alias: 'j', method: 'foo' } } - }); - }); - - it('when \'on\' and \'orOn\' is missing in join definition', function() { - - // testing QueryBuilder._validateJoin - - assertThrowsBuildJoins({ - params: { joins: ['joinA'] }, - fields: { foo: true }, - joins: { joinA: { alias: 'j', method: 'left' } } - }); - }); - - it('when \'on\' and \'orOn\' are invalid in join definition', function() { - - // testing QueryBuilder._validateJoin - - const invalidOns = [ - true, - 123, - { foo: 'bar' }, - [], - [1], - ['foo'], - ['foo', 1], - ['foo', 'bar', 1], - ['foo', 'badOperator', 'bar'], - [[1], [2]], - [['foo'], ['bar']], - [['foo', 'bar'], ['bar']], - [['foo'], ['foo', 'bar']], - [['foo', 'bar'], ['foo', 'badOperator', 'bar']], - [['foo', 'badOperator', 'bar'], ['foo', 'bar']] - ]; - - invalidOns.forEach(invalidOn => { - - const params = { - params: { joins: ['tableA'] }, - fields: { foo: true, bar: true }, - joins: { tableA: { alias: 'j', on: invalidOn } } - }; - - assertThrowsBuildJoins(params); - - delete params.joins.tableA.on; - params.joins.tableA.orOn = invalidOn; - - assertThrowsBuildJoins(params); - }); - }); - }); - - describe('Should join', function() { - - const assertJoinAndGetFakeKnex = (queryBuilderParams, joinTable, joinMethod) => { - - const queryBuilder = queryBuilderFactory(queryBuilderParams); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement[joinMethod].calledOnce); - - assert.equal(queryBuilder.knexStatement[joinMethod].args[0][0], joinTable); - - const joinCallback = queryBuilder.knexStatement[joinMethod].args[0][1]; - - assert.equal(typeof joinCallback, 'function'); - - const fakeKnex = makeKnex(); - - joinCallback(fakeKnex); - - return fakeKnex; - }; - - it('Should call \'join\' knex method when valid join configuration', function() { - - const queryBuilderParams = { - params: { joins: ['tableB'] }, - fields: { foo: true, bar: { table: 'tableB' } }, - joins: { tableB: { alias: 'j', on: ['foo', 'bar'] } } - }; - - const fakeKnex = assertJoinAndGetFakeKnex(queryBuilderParams, 'tableB as j', 'leftJoin'); - - assert.equal(fakeKnex.on.callCount, 1); - - assert.deepEqual(fakeKnex.on.args[0], ['t.foo', 'j.bar']); - - assert.equal(fakeKnex.orOn.callCount, 0); - }); - - it('Should call \'join\' knex method when valid join with multiple \'on\' configuration', function() { - - const queryBuilderParams = { - params: { joins: ['tableB'] }, - fields: { foo: true, bar: { table: 'tableB' }, foo2: true, bar2: { table: 'tableB' } }, - joins: { - tableB: { - method: 'join', - alias: 'j', - on: [['foo', 'bar'], ['foo2', 'bar2']] - } - } - }; - - const fakeKnex = assertJoinAndGetFakeKnex(queryBuilderParams, 'tableB as j', 'join'); - - assert.equal(fakeKnex.on.callCount, 2); - - assert.deepEqual(fakeKnex.on.args[0], ['t.foo', 'j.bar']); - assert.deepEqual(fakeKnex.on.args[1], ['t.foo2', 'j.bar2']); - - assert.equal(fakeKnex.orOn.callCount, 0); - }); - - it('Should call \'join\' knex method when valid join with multiple \'orOn\' configuration', function() { - - const queryBuilderParams = { - params: { joins: ['tableB'] }, - fields: { - foo: true, - bar: { table: 'tableB' }, - foo2: true, - bar2: { table: 'tableB' }, - foo3: true, - bar3: { table: 'tableB' } - }, - joins: { - tableB: { - alias: 'j', - method: 'fullOuter', - orOn: [ - ['foo', 'bar'], - ['foo2', '=', 'bar2'], - ['foo3', '!=', 'bar3'] - ] - } - } - }; - - const fakeKnex = assertJoinAndGetFakeKnex(queryBuilderParams, 'tableB as j', 'fullOuterJoin'); - - assert.equal(fakeKnex.on.callCount, 0); - assert.equal(fakeKnex.orOn.callCount, 3); - - assert.deepEqual(fakeKnex.orOn.args[0], ['t.foo', 'j.bar']); - assert.deepEqual(fakeKnex.orOn.args[1], ['t.foo2', '=', 'j.bar2']); - assert.deepEqual(fakeKnex.orOn.args[2], ['t.foo3', '!=', 'j.bar3']); - }); - }); - }); - - describe('buildFilters', function() { - - const callWhereCallback = (queryBuilder, whereMethod = 'where', callIndex = 0) => { - - const whereCallback = queryBuilder.knexStatement[whereMethod].args[callIndex][0]; - - assert.equal(typeof whereCallback, 'function'); - - const fakeKnex = makeKnex(); - - whereCallback(fakeKnex); - - return fakeKnex; - }; - - describe('Shouldn\t call knex.where methods', function() { - - it('when no filters passed', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.notCalled); - }); - - it('when filters passed but missed definition', function() { - - const queryBuilder = queryBuilderFactory({ - params: { filters: { id: 1 } } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.notCalled); - }); - }); - - describe('Should throws', function() { - - const assertThrowsWhenBuildAndWhereCallback = queryBuilderParams => { - - const queryBuilder = queryBuilderFactory(queryBuilderParams); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - assert.throws(() => { callWhereCallback(queryBuilder); }, QueryBuilderError); - }; - - it('when non-existent filters passed', function() { - assertThrowsWhenBuildAndWhereCallback({ - params: { filters: { id: 1 } }, - fields: { foo: 'foo' } - }); - }); - - it('when invalid filter type passed', function() { - assertThrowsWhenBuildAndWhereCallback({ - params: { filters: { id: { field: 'id', type: 'foo', value: 1 } } }, - fields: { id: 'id' } - }); - }); - - it('when passed a filter that denies multiple values', function() { - assertThrowsWhenBuildAndWhereCallback({ - params: { filters: { id: { field: 'id', type: 'greater', value: [1, 2, 3] } } }, - fields: { id: 'id' } - }); - }); - - it('when passed a filter that needs multiple values', function() { - assertThrowsWhenBuildAndWhereCallback({ - params: { filters: { id: { field: 'id', type: 'between', value: 1 } } }, - fields: { id: 'id' } - }); - }); - - it('when passed a filter that needs specific quantity of multiple values', function() { - - assertThrowsWhenBuildAndWhereCallback({ - params: { filters: { id: { field: 'id', type: 'between', value: [1, 2, 3] } } }, - fields: { id: 'id' } - }); - }); - }); - - describe('Should call knex.where methods', function() { - - it('Should call \'where\' knex method when filter passed', function() { - - const params = { filters: { id: 1 } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', 1]); - }); - - it('Should call \'whereIn\' knex method when filter with multiple values passed', function() { - - const params = { filters: { id: { value: [1, 2] } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereIn.callCount, 1); - assert.deepEqual(fakeKnex.whereIn.args[0], ['t.id', [1, 2]]); - }); - - it('Should call \'orWhere\' knex method once when filter as array are passed', function() { - - const params = { filters: [{ id: 1 }] }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.orWhere.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder, 'orWhere'); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', 1]); - }); - - it('Should call \'orWhere\' knex method twice when 2 filter in an array are passed', function() { - - const params = { - filters: [{ id: 1 }, { id: 3 }] - }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.orWhere.calledTwice); - - const fakeKnex = callWhereCallback(queryBuilder, 'orWhere'); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', 1]); - - const fakeKnex2 = callWhereCallback(queryBuilder, 'orWhere', 1); - - assert.equal(fakeKnex2.where.callCount, 1); - assert.deepEqual(fakeKnex2.where.args[0], ['t.id', 3]); - }); - - it('Should call \'whereNot\' knex method when \'notEqual\' filter passed', function() { - - const params = { filters: { id: { value: 98, type: 'notEqual' } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereNot.callCount, 1); - assert.deepEqual(fakeKnex.whereNot.args[0], ['t.id', 98]); - }); - - it('Should call \'whereNotIn\' knex method when \'notEqual\' filter with multiple values passed', function() { - - const params = { filters: { id: { value: [1, 2], type: 'notEqual' } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereNotIn.callCount, 1); - assert.deepEqual(fakeKnex.whereNotIn.args[0], ['t.id', [1, 2]]); - }); - - it('Should call \'where\' knex method with \'>\' when \'greater\' filter passed', function() { - - const params = { filters: { id: { value: 1, type: 'greater' } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', '>', 1]); - }); - - it('Should call \'where\' knex method with \'>=\' when \'greaterOrEqual\' filter passed', function() { - - const params = { filters: { id: { value: 1, type: 'greaterOrEqual' } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', '>=', 1]); - }); - - it('Should call \'where\' knex method with \'<\' when \'lesser\' filter passed', function() { - - const params = { filters: { id: { value: 1, type: 'lesser' } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', '<', 1]); - }); - - it('Should call \'where\' knex method with \'<=\' when \'lesserOrEqual\' filter passed', function() { - - const params = { filters: { id: { value: 1, type: 'lesserOrEqual' } } }; - const fields = { id: 'id' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.id', '<=', 1]); - }); - - it('Should call \'where\' knex method with \'LIKE\' when \'search\' filter passed', function() { - - const params = { filters: { foo: { value: 'foo', type: 'search' } } }; - const fields = { foo: 'foo' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.foo', 'LIKE', '%foo%']); - }); - - it('Should call \'whereBetween\' knex method when \'between\' filter passed', function() { - - const params = { filters: { foo: { value: ['hey', 'there'], type: 'between' } } }; - const fields = { foo: 'foo' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereBetween.callCount, 1); - assert.deepEqual(fakeKnex.whereBetween.args[0], ['t.foo', ['hey', 'there']]); - }); - - it('Should call \'whereNotBetween\' knex method when \'notBetween\' filter passed', function() { - - const params = { filters: { foo: { value: ['hey', 'there'], type: 'notBetween' } } }; - const fields = { foo: 'foo' }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereNotBetween.callCount, 1); - assert.deepEqual(fakeKnex.whereNotBetween.args[0], ['t.foo', ['hey', 'there']]); - }); - - it('Should call \'whereNull\' knex method with \'t.foo\' when null type filter passed', function() { - - const params = { filters: { foo: { type: 'null' } } }; - const fields = { foo: true }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereNull.callCount, 1); - assert.deepEqual(fakeKnex.whereNull.args[0], ['t.foo']); - }); - - it('Should call \'whereNotNull\' knex method with \'t.foo\' when notNull type filter passed', function() { - - const params = { filters: { foo: { type: 'notNull' } } }; - const fields = { foo: true }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereNotNull.callCount, 1); - assert.deepEqual(fakeKnex.whereNotNull.args[0], ['t.foo']); - }); - - it('Should call \'where\' knex method with \'LIKE\' when \'search\' in fields definition', function() { - - const params = { filters: { foo: { value: 'foo' } } }; - const fields = { foo: { type: 'search' } }; - - const queryBuilder = queryBuilderFactory({ params, fields }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.where.callCount, 1); - assert.deepEqual(fakeKnex.where.args[0], ['t.foo', 'LIKE', '%foo%']); - }); - - it('Should call \'whereRaw\' knex method for flags filters', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { - status: true, - isActive: true, - error: true - }, - flags: { - status: { isActive: 1, error: 2 } - }, - params: { - filters: { isActive: true, error: false } - } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.where.calledOnce); - - const fakeKnex = callWhereCallback(queryBuilder); - - assert.equal(fakeKnex.whereRaw.callCount, 2); - assert.deepEqual(fakeKnex.whereRaw.args[0], ['(t.status & 1) = ?', '1']); - assert.deepEqual(fakeKnex.whereRaw.args[1], ['(t.status & 2) = ?', '0']); - - }); - - }); - }); - - describe('buildOrder', function() { - - describe('Shouldn\'t call knex.orderBy', function() { - - it('when params.order not passed', function() { - - const queryBuilder = queryBuilderFactory(); - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.orderBy.called, false); - - }); - - it('when params.order passed but no fields', function() { - - // order passed but no fields - - const queryBuilder = queryBuilderFactory({ - params: { order: 'id' } - }); - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.orderBy.called, false); - }); - }); - - describe('Should throws', function() { - - it('when format invalid order passed', function() { - - assertThrowsWhenBuild(queryBuilderFactory({ - fields: { id: true }, - params: { order: ['id'] } - })); - }); - - it('when invalid direction in order passed', function() { - - assertThrowsWhenBuild(queryBuilderFactory({ - fields: { id: true }, - params: { order: { id: 'foo' } } - })); - }); - - it('when field not present in definition', function() { - - assertThrowsWhenBuild(queryBuilderFactory({ - fields: { foo: true }, - params: { order: 'bar' } - })); - }); - }); - - describe('Should call knex.orderBy', function() { - - it('when simple \'params.order\' passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: 'id' }, - params: { order: 'id' } - }); - - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.orderBy.called, true); - assert.equal(queryBuilder.knexStatement.orderBy.calledOnce, true); - - assert.deepEqual(queryBuilder.knexStatement.orderBy.args[0], ['t.id', 'asc']); - }); - - it('when complex \'params.order\' passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: 'id' }, - params: { order: { id: 'desc' } } - }); - - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.orderBy.calledOnce, true); - - assert.deepEqual(queryBuilder.knexStatement.orderBy.args[0], ['t.id', 'desc']); - }); - - it('when multiple \'params.order\' passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { id: 'id', name: 'name', date: 'date' }, - params: { order: { name: 'desc', id: 'asc' } } - }); - - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.orderBy.calledTwice, true); - - assert.deepEqual(queryBuilder.knexStatement.orderBy.args[0], ['t.name', 'desc']); - assert.deepEqual(queryBuilder.knexStatement.orderBy.args[1], ['t.id', 'asc']); - }); - }); - - describe('Should call knex.orderByRaw', function() { - it('when flag field in \'params.order\' passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { - status: true, - isActive: true - }, - flags: { status: { isActive: 1 } }, - params: { order: 'isActive' } - }); - - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.orderByRaw.calledOnce, true); - - assert.deepEqual(queryBuilder.knexStatement.orderByRaw.args[0], ['(t.status & 1) asc']); - }); - }); - }); - - describe('buildLimit - Limit', function() { - - describe('Shouldn\'t call knex.limit', function() { - - it('when \'params.limit\' not passed', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.limit.notCalled); - }); - }); - - describe('Should throws', function() { - - it('when invalid \'params.limit\' passed', function() { - - const invalidLimits = [ - 'foo', - ['foo', 'bar'], - { foo: 'bar' } - ]; - - invalidLimits.forEach(invalidLimit => assertThrowsWhenBuild(queryBuilderFactory({ params: { limit: invalidLimit } }))); - }); - }); - - describe('Should call knex.limit', function() { - - it('when valid \'params.limit\' passed', function() { - - const queryBuilder = queryBuilderFactory({ params: { limit: 1 } }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.limit.calledOnce); - assert.deepEqual(queryBuilder.knexStatement.limit.args[0], [1]); - }); - - it('when valid \'params.limit\' and \'param.page\' passed', function() { - - const queryBuilder = queryBuilderFactory({ params: { limit: 5, page: 1 } }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.limit.calledOnce); - assert.deepEqual(queryBuilder.knexStatement.limit.args[0], [5]); - }); - }); - }); - - describe('buildGroup', function() { - - describe('Shouldn\'t call knex.groupBy', function() { - - it('when \'params.group\' not passed', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.groupBy.notCalled); - }); - - it('when \'params.group\' as false passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true }, - params: { group: false } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.groupBy.notCalled); - }); - - it('when \'params.group\' passed but missed fields definition', function() { - - const queryBuilder = queryBuilderFactory({ - params: { group: { foo: 'bar' } } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.groupBy.notCalled); - }); - }); - - describe('Should throws', function() { - - it('when invalid \'params.group\' passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true }, - params: { group: { foo: 'bar' } } - }); - - assertThrowsWhenBuild(queryBuilder); - }); - - it('when valid \'params.group\' passed but unknown field', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true }, - params: { group: 'bar' } - }); - - assertThrowsWhenBuild(queryBuilder); - }); - - it('when valid \'params.group\' passed but unknown field in an array', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true }, - params: { group: ['bar'] } - }); - - assertThrowsWhenBuild(queryBuilder); - }); - - it('when \'params.group\' as an empty array', function() { - - const queryBuilder = queryBuilderFactory({ params: { group: [] }, fields: { foo: true } }); - - assertThrowsWhenBuild(queryBuilder); - - assert(queryBuilder.knexStatement.groupBy.notCalled); - }); - - }); - - describe('Should call knex.groupBy', function() { - - it('when valid \'params.group\' field passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true }, - params: { group: 'foo' } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.groupBy.calledOnce); - assert.deepEqual(queryBuilder.knexStatement.groupBy.args[0], ['t.foo']); - }); - - it('when valid \'params.group\' array of fields passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { foo: true, bar: true }, - params: { group: ['foo', 'foo', 'bar'] } // arrayUnique for avoid repetition - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.groupBy.calledTwice); - assert.deepEqual(queryBuilder.knexStatement.groupBy.args[0], ['t.foo']); - assert.deepEqual(queryBuilder.knexStatement.groupBy.args[1], ['t.bar']); - }); - - }); - - describe('Should call knex.groupByRaw', function() { - - it('when \'params.group\' with a flag field passed', function() { - - const queryBuilder = queryBuilderFactory({ - fields: { - status: true, - isActive: { field: 'status', flag: 1 } - }, - flags: { status: { isActive: 1 } }, - params: { group: 'isActive' } - }); - - queryBuilder.build(); - - assert.equal(queryBuilder.knexStatement.groupByRaw.calledOnce, true); - - assert.deepEqual(queryBuilder.knexStatement.groupByRaw.args[0], ['(t.status & 1)']); - }); - }); - }); - - describe('buildLimit - Offset and Page', function() { - - describe('Shouldn\'t call knex.offset', function() { - - it('when \'params.offset\' not passed', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.offset.notCalled); - }); - }); - - describe('Should throws', function() { - - it('when invalid \'params.offset\' passed', function() { - - const invalidOffsets = [ - 'foo', - ['foo', 'bar'], - { foo: 'bar' } - ]; - - invalidOffsets.forEach(invalidOffset => assertThrowsWhenBuild(queryBuilderFactory({ params: { offset: invalidOffset } }))); - }); - - it('when invalid \'params.page\' passed', function() { - - const invalidPages = [ - 'foo', - true, - ['foo', 'bar'], - { foo: 'bar' } - ]; - - invalidPages.forEach(invalidPage => assertThrowsWhenBuild(queryBuilderFactory({ params: { page: invalidPage } }))); - }); - - it('when valid \'params.page\' but no \'params.limit\' passed', function() { - assertThrowsWhenBuild(queryBuilderFactory({ params: { page: 1 } })); - }); - }); - - describe('Should call knex.offset', function() { - - it('when \'params.offset\' passed', function() { - - const queryBuilder = queryBuilderFactory({ params: { offset: 3 } }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.offset.calledOnce); - - assert.deepEqual(queryBuilder.knexStatement.offset.args[0], [3]); - }); - - it('when \'param.limit\' and \'param.page\' passed', function() { - - const queryBuilder = queryBuilderFactory({ params: { limit: 5, page: 3 } }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.limit.calledOnce - && queryBuilder.knexStatement.offset.calledOnce); - - assert.deepEqual(queryBuilder.knexStatement.limit.args[0], [5]); - assert.deepEqual(queryBuilder.knexStatement.offset.args[0], [10]); - - }); - }); - }); - - describe('flags', function() { - - it('Should throws - when wrong flag reference field', function() { - - const queryBuilder = queryBuilderFactory({ - params: { - fields: ['isActive'] - }, - fields: { - isActive: true - // status missing - }, - flags: { - status: { isActive: 1 } - } - }); - - assertThrowsWhenBuild(queryBuilder); - - assert(!queryBuilder.knexStatement.select.called); - }); - - it('Should\'t use flag', function() { - - const queryBuilder = queryBuilderFactory({ - params: { - fields: ['status'] - }, - fields: { - status: true, - isActive: true - }, - flags: { - status: { isActive: 1 } - } - }); - - queryBuilder.build(); - - assert(queryBuilder.knexStatement.select.calledOnce); - }); - }); - - describe('execute', function() { - - it('Should return knexStatement', function() { - - const queryBuilder = queryBuilderFactory(); - - queryBuilder.build(); - - const executeSpy = sinon.spy(queryBuilder, 'execute'); - - queryBuilder.execute(); - - assert(executeSpy.returnValues[0] instanceof Promise); - }); - }); -}); From 2cb32dfd1479d69e487d4c219962775e8b23c69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Pereyra?= Date: Fri, 31 May 2019 15:06:50 -0300 Subject: [PATCH 02/35] Added Travis and Coveralls badges --- .travis.yml | 15 +++++++++++++++ CHANGELOG.md | 6 +++++- README.md | 5 ++++- package.json | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c329f05 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: node_js +node_js: + - "lts/*" +cache: npm +script: + - | + # Run test script + npm run test-ci +after_script: + - | + # Upload coverage to coveralls + if [[ -d .nyc_output ]]; then + npm install --save-dev coveralls@2 + nyc report --reporter=text-lcov | coveralls + fi \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca6f06..e5e9a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,4 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - MySQL wrapper - QueryBuilder - janiscommerce/logger -- Tests \ No newline at end of file +- Tests + +## [Unreleased] +### Removed +- `Query Builder` moved to an independent package \ No newline at end of file diff --git a/README.md b/README.md index 0d766f4..a1e9434 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# mysql \ No newline at end of file +# MySql + +[![Build Status](https://travis-ci.org/janis-commerce/mysql.svg?branch=JCN-49-janis-mysql)](https://travis-ci.org/janis-commerce/mysql) +[![Coverage Status](https://coveralls.io/repos/github/janis-commerce/mysql/badge.svg?branch=JCN-49-janis-mysql)](https://coveralls.io/github/janis-commerce/mysql?branch=JCN-49-janis-mysql) \ No newline at end of file diff --git a/package.json b/package.json index 8f1a58a..e71a6b3 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "test": "export TEST_ENV=true; mocha --exit -R nyan --recursive tests/", "watch-test": "export TEST_ENV=true; mocha --exit -R nyan -w --recursive", + "test-ci": "nyc --reporter=html --reporter=text mocha --recursive tests/", "coverage": "nyc npm test" }, "repository": { From ee7646fb3ae3bc2093c440c1fa7d797f74bc71e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Pereyra?= Date: Fri, 31 May 2019 18:13:32 -0300 Subject: [PATCH 03/35] Updated docs --- README.md | 60 +++++++++++++++++++++++++++++++- mysql/mysql.js | 42 +++++++++++++++++++---- tests/mysql-test.js | 83 +++++++++++++++++++++++++++++++-------------- 3 files changed, 152 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a1e9434..324fcc5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,62 @@ # MySql [![Build Status](https://travis-ci.org/janis-commerce/mysql.svg?branch=JCN-49-janis-mysql)](https://travis-ci.org/janis-commerce/mysql) -[![Coverage Status](https://coveralls.io/repos/github/janis-commerce/mysql/badge.svg?branch=JCN-49-janis-mysql)](https://coveralls.io/github/janis-commerce/mysql?branch=JCN-49-janis-mysql) \ No newline at end of file +[![Coverage Status](https://coveralls.io/repos/github/janis-commerce/mysql/badge.svg?branch=JCN-49-janis-mysql)](https://coveralls.io/github/janis-commerce/mysql?branch=JCN-49-janis-mysql) + +## Installation + +``` +npm install @janiscommerce/mysql +``` + +## API + +* `new MySQL(config)` + + MySQL driver Module. `config` object with the database configuration. + +* `save(model, item)` + + Saved an item in the database. Returns the Row affected + - `model` a Model class, used to setup correctly the insertion. + - `item` the object to be saved. + +* `get(model, parametres)` + + Search in the database and return a `Promise` with the results. + - `model` a Model class, used to get the correct search. + - `parametres` it's an `object` with the field and options to make the query. + +* `insert(model, values, allowUpserted)` + + Insert or Update a Row. Returns the Row affected + +* `update(model, item)` + + Update a row. + +* `multiInsert(model, items)` + + Performs a multi insert. + +## Config + +The configuration object looks like: + +```javascript + +const config = { + host: 'someHost', // host name where the database is connected + user: 'yourUser', // username + password: 'yourPassword', // password + database: 'your_database_name', // the database name, could not exist + port: 3006, // the port where the database is connected + connectionLimit: 5000, // A connection limit, 5000 by default + prefix: 'yourPrefix' // if you use some prefix, could not exist +} + +``` + +- - - + +## Usage \ No newline at end of file diff --git a/mysql/mysql.js b/mysql/mysql.js index 6ec7462..9fac4ee 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -37,10 +37,17 @@ class MySQL { return '_joins'; } + /** + * @returns {object} connection pool + */ static get connectionPool() { return this._connectionPool || {}; } + /** + * Set a connection pool + * @param {object} connection Pool connection data + */ static set connectionPool(connection) { if(!this._connectionPool) @@ -53,6 +60,10 @@ class MySQL { }; } + /** + * Constructor + * @param {object} config Database configuration. + */ constructor(config) { this.config = { host: config.host, @@ -66,6 +77,10 @@ class MySQL { }; } + /** + * Returns a MySQL pool, if it didn't exist create one. + * @returns {object} MySQL pool instance + */ get pool() { if(!this._pool) @@ -74,7 +89,6 @@ class MySQL { return this._pool; } - /* istanbul ignore next */ get knex() { if(!this._knex) { @@ -93,6 +107,13 @@ class MySQL { return this._knex; } + /** + * Search in the database. + * + * @param {Class} model Model Class + * @param {object} params object with the parametres to search. + * @returns {Promise} Results of the search + */ async get(model, params) { const newParams = { ...params }; // necesario para no alterar params y no afectar a las keys de cache @@ -157,13 +178,22 @@ class MySQL { return fields; } + /** + * Save an item in the SQL database + * @param {Class} model Model Class + * @param {object} item object to saved + * @returns {number} Rows affected + */ async save(model, item) { return this.insert(model, item, true); } /** * Insert/update a row + * @param {Class} model - Model Class * @param {object} item - The item to insert + * @param {boolean} allowUpsert - If upsert is allowed + * @returns {number} Rows affected */ async insert(model, item, allowUpsert = false) { @@ -285,7 +315,6 @@ class MySQL { return this.call(statement, placeholders); } - /* istanbul ignore next */ call(query, placeholders = {}) { return new Promise((resolve, reject) => { this.query(query, placeholders, (err, rows) => { @@ -297,7 +326,6 @@ class MySQL { }); } - /* istanbul ignore next */ query(statement, placeholders, callback) { this.pool.getConnection((connErr, connection) => { @@ -643,7 +671,7 @@ class MySQL { return parsed; } - /* istanbul ignore next */ + getConnection() { return new Promise((resolve, reject) => { @@ -661,7 +689,7 @@ class MySQL { }); } - /* istanbul ignore next */ + end() { return new Promise((resolve, reject) => { @@ -687,7 +715,7 @@ class MySQL { return query; return query.replace(/:(\w+)/g, (txt, key) => { - + console.log(txt) if(values.hasOwnProperty(key)) return mysql.escape(values[key]); @@ -738,7 +766,7 @@ class MySQL { /** * No need to create indexes in this Database */ - /* istanbul ignore next */ + async createIndexes() { return true; } diff --git a/tests/mysql-test.js b/tests/mysql-test.js index d8b8643..42bb650 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -52,6 +52,64 @@ describe('MySQL', function() { stubBuild.restore(); }); + describe('static getters', () => { + it('should return default limit', () => { + const DEFAULT_LIMIT = 500; + + assert.equal(DEFAULT_LIMIT, MySQL.defaultLimit); + }); + + it('should return max iddle timeout', () => { + const MAX_IDDLE_TIMEOUT = 60 * 5; + + assert.equal(MAX_IDDLE_TIMEOUT, MySQL.maxIddleTimeout); + }); + + it('should return "_filters"', () => { + assert.equal('_filters', MySQL.filters); + }); + + it('should return "_joins"', () => { + assert.equal('_joins', MySQL.joins); + }); + + it('should return "_columns"', () => { + assert.equal('_columns', MySQL.columns); + }); + + }); + + describe('connecionPool - setter and getter', function() { + + it('should return empty object when no pool connection was set', () => { + + assert(typeof MySQL.connectionPool, 'Object'); + + assert.deepEqual({}, MySQL.connectionPool, 'it should by {}'); + }); + + it('should set a connection', function() { + + const threadId = 123; + + MySQL.connectionPool = { threadId }; + + assert(typeof MySQL.connectionPool, 'Object'); + + assert(typeof MySQL.connectionPool[threadId], 'Object'); + + assert(typeof MySQL.connectionPool[threadId].lastActivity, 'number'); + + const now = Date.now() / 1000 | 0; + + assert(MySQL.connectionPool[threadId].lastActivity >= now); + + assert.equal(MySQL.connectionPool[threadId].id, threadId); + + }); + + }); + describe('get() getTotals()', function() { const stubExecute = result => sinon.stub(QueryBuilder.prototype, 'execute').callsFake(() => result); @@ -320,30 +378,6 @@ describe('MySQL', function() { }); - describe('connecionPool - setter and getter', function() { - - it('should set a connection', function() { - - const threadId = 123; - - MySQL.connectionPool = { threadId }; - - assert(typeof MySQL.connectionPool, 'Object'); - - assert(typeof MySQL.connectionPool[threadId], 'Object'); - - assert(typeof MySQL.connectionPool[threadId].lastActivity, 'number'); - - const now = Date.now() / 1000 | 0; - - assert(MySQL.connectionPool[threadId].lastActivity >= now); - - assert.equal(MySQL.connectionPool[threadId].id, threadId); - - }); - - }); - describe('Build field query', function() { it('Should build field query correctly for a single value field', function() { @@ -519,5 +553,4 @@ describe('MySQL', function() { }); }); - }); From 9611fd24e2b1a622d1e084719c4d679234dd5f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Pereyra?= Date: Mon, 3 Jun 2019 18:18:59 -0300 Subject: [PATCH 04/35] Changing to Mysql2 module --- README.md | 2 + mysql/mysql-promise.js | 163 ++++++++++++++++++++++++++++++++++++ mysql/mysql.js | 91 ++++++++++++-------- tests/mysql-test.js | 184 ++++++++++++++++++++++++----------------- 4 files changed, 329 insertions(+), 111 deletions(-) create mode 100644 mysql/mysql-promise.js diff --git a/README.md b/README.md index 324fcc5..c438b17 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ npm install @janiscommerce/mysql Performs a multi insert. +* `remove()` + ## Config The configuration object looks like: diff --git a/mysql/mysql-promise.js b/mysql/mysql-promise.js new file mode 100644 index 0000000..0c26657 --- /dev/null +++ b/mysql/mysql-promise.js @@ -0,0 +1,163 @@ +'use strict'; + +const mysql = require('mysql2/promise'); +const knex = require('knex'); + +const logger = require('@janiscommerce/logger'); +const QueryBuilder = require('@janiscommerce/query-builder'); + +const MySQLError = require('./mysql-error'); + +const Utils = require('./../utils'); + +const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds +const CONNECTION_LIMIT = 10; + +const DEFAULT_LIMIT = 500; + +class MySQL { + + static get defaultLimit() { + return DEFAULT_LIMIT; + } + + static get maxIddleTimeout() { + return MAX_IDDLE_TIMEOUT; + } + + static get filters() { + return '_filters'; + } + + static get columns() { + return '_columns'; + } + + static get joins() { + return '_joins'; + } + + /** + * @returns {object} connection pool + */ + static get connectionPool() { + return this._connectionPool || {}; + } + + /** + * Set a connection pool + * @param {object} connection Pool connection data + */ + static set connectionPool(connection) { + + if(!this._connectionPool) + this._connectionPool = {}; + + this._connectionPool[connection.threadId] = { + connection, + lastActivity: (new Date() / 1000 | 0), + id: connection.threadId + }; + } + + /** + * Constructor + * @param {object} config Database configuration. + */ + constructor(config) { + this.config = { + host: config.host, + user: config.user, + password: config.password, + database: config.database || null, + port: config.port, + connectionLimit: config.connectionLimit || CONNECTION_LIMIT, + multipleStatements: true, + prefix: config.prefix || '' + }; + } + + /** + * Returns a MySQL pool, if it didn't exist create one. + * @returns {object} MySQL pool instance + */ + get pool() { + + if(!this._pool) + this._pool = mysql.createPool(this.config); + + return this._pool; + } + + /* istanbul ignore next */ + _queryFormat(query, values) { + if(!values) + return query; + + return query.replace(/:(\w+)/g, (txt, key) => { + if(values.hasOwnProperty(key)) + return mysql.escape(values[key]); + + return txt; + }); + } + + /** + * Async + * @returns {Promise} MySQL connection + */ + getConnection() { + return new Promise(async(resolve, reject) => { + + try { + + const { connection } = await this.pool.getConnection(); + connection.config.queryFormat = this._queryFormat.bind(this); + this.constructor.connectionPool = connection; + resolve(await this.pool.getConnection()); + + } catch(error) { + reject(error); + } + }); + } + + /** + * Execute de Query in the database + * @param {String} query Query to be executed + * @param {*} placeholders + */ + _call(query, placeholders = {}) { + return new Promise(async(resolve, reject) => { + try { + const connection = await this.getConnection(); + const rowsAffected = await connection.query(query, placeholders); + connection.release(); + resolve(rowsAffected); + /* connection.query(query, placeholders, (err, rows) => { + + if(err) { + logger.error('query', err.errno, err.code, err.message); + logger.debug(query, placeholders); + reject(err); + } + + connection.release(); + resolve(rows); + }); */ + + } catch(error) { + if(error.code === 'ER_CON_COUNT_ERROR') // Too Many Connections + return setTimeout(() => this._call.apply(this, arguments), 500); // Retry + + logger.error('Database', error.message); + reject(error); + + } + }); + } + + +} + +module.exports = MySQL; diff --git a/mysql/mysql.js b/mysql/mysql.js index 9fac4ee..81f7029 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -258,7 +258,6 @@ class MySQL { * Update a row * @param {object} item - The date to update */ - async update(model, item) { const table = model.getTable(); @@ -316,16 +315,60 @@ class MySQL { } call(query, placeholders = {}) { - return new Promise((resolve, reject) => { + return new Promise(async(resolve, reject) => { + try { + const connection = await this.getConnection(); + connection.query(query, placeholders, (err, rows) => { + + if(err) { + logger.error('query', err.errno, err.code, err.message); + logger.debug(query, placeholders); + reject(err); + } + + connection.release(); + resolve(rows); + }); + + } catch(error) { + if(error.code === 'ER_CON_COUNT_ERROR') // Too Many Connections + return setTimeout(() => this.call.apply(this, arguments), 500); // Retry + + logger.error('Database', error); + reject(error); + + } + /* this.query(query, placeholders, (err, rows) => { if(err) reject(err); resolve(rows); + }); */ + }); + } + + /** + * Async + * @returns {Promise} MySQL connection + */ + getConnection() { + return new Promise((resolve, reject) => { + + this.pool.getConnection((err, connection) => { + if(err) + reject(err); + + connection.config.queryFormat = this.queryFormat.bind(this); + + this.constructor.connectionPool = connection; + resolve(connection); }); + }); } +/* query(statement, placeholders, callback) { this.pool.getConnection((connErr, connection) => { @@ -361,13 +404,12 @@ class MySQL { }); }); } - + */ /** * Perform a multi insert * @param {array} items - The items to insert * @param {string} [table=this.constructor.table] - The table */ - async multiInsert(model, items) { if(!items || !items.length) { @@ -485,6 +527,7 @@ class MySQL { * @param {string} table The table * @return {object} { where and placeholders } */ + async prepareFields(model, fields, tableAlias = '', suffix = '') { let where = []; @@ -532,6 +575,7 @@ class MySQL { * @param {Array.<{table: String, type: String, alias: String, condition: String}>} joins - Array of shape [{ table, type, alias, condition }] * @returns {string} */ + buildJoins(joins) { if(!joins || !Array.isArray(joins)) return ''; @@ -606,9 +650,7 @@ class MySQL { * */ - mapFields(data, map) { - if(Array.isArray(data)) return data.map(item => this.mapItem(item, map)); @@ -622,10 +664,7 @@ class MySQL { * @param {object} [map=this.fieldsMap] - The map * @private */ - - mapItem(item, map) { - const fields = {}; // We use Reflect and not Object.entries/Keys @@ -646,19 +685,18 @@ class MySQL { */ mapField(field, map) { - if(typeof field === 'symbol') return field; let parsed = null; - Object.entries(map || this.fieldsMap || {}).forEach(([key, value]) => { + /* Object.entries(map || this.fieldsMap || {}).forEach(([key, value]) => { value = Array.isArray(value) ? value : [value]; if(value.includes(field)) parsed = key; - }); + }); */ if(!parsed) { // Replace camel case to lower dash format. @@ -671,25 +709,7 @@ class MySQL { return parsed; } - - getConnection() { - return new Promise((resolve, reject) => { - - this.pool.getConnection((err, connection) => { - if(err) - reject(err); - - connection.config.queryFormat = this.queryFormat.bind(this); - - this.constructor.connectionPool = connection; - - resolve(connection); - }); - - }); - } - - + /* istanbul ignore next */ end() { return new Promise((resolve, reject) => { @@ -710,12 +730,12 @@ class MySQL { }); } + /* istanbul ignore next */ queryFormat(query, values) { if(!values) return query; return query.replace(/:(\w+)/g, (txt, key) => { - console.log(txt) if(values.hasOwnProperty(key)) return mysql.escape(values[key]); @@ -726,8 +746,9 @@ class MySQL { shouldDestroyConnectionPool(lastActivity) { return !lastActivity || ((Date.now() / 1000 | 0) - lastActivity > this.constructor.maxIddleTimeout); } - + /* istanbul ignore next */ + closeIddleConnections() { if(this.closeIddleConnectionsInterval) @@ -766,10 +787,10 @@ class MySQL { /** * No need to create indexes in this Database */ - + /* async createIndexes() { return true; - } + } */ } module.exports = MySQL; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 42bb650..7a4bfd2 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -110,82 +110,6 @@ describe('MySQL', function() { }); - describe('get() getTotals()', function() { - - const stubExecute = result => sinon.stub(QueryBuilder.prototype, 'execute').callsFake(() => result); - - const testParams = (params, expectedParams) => { - assert.deepEqual(params, expectedParams, 'shouldn\'t modify ofiginal params'); - }; - - it('Should return empty results and totals with zero values', async function() { - - const params = {}; - - const stub = stubExecute([]); - - const result = await mysql.get(dummyModel, params); - - assert.deepEqual(result, []); - - const resultTotals = await mysql.getTotals(dummyModel); - - assert.deepEqual(resultTotals, { total: 0, pages: 0 }); - - testParams(params, {}); - - stub.restore(); - }); - - it('Should return results and totals', async function() { - - const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; - const params = { ...originalParams }; - - const stubResults = stubExecute([{ result: 1 }, { result: 2 }]); - - const result = await mysql.get(dummyModel, params); - - assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); - - testParams(params, originalParams); - - stubResults.restore(); - - const stubTotals = stubExecute([{ count: 650 }]); - - const resultTotals = await mysql.getTotals(dummyModel); - - assert.deepEqual(resultTotals, { - total: 650, - page: 4, - pageSize: 10, - pages: 65 - }); - - stubTotals.restore(); - }); - }); - - describe('getFields()', function() { - - it('Should return formatted fields', async function() { - - const stubCall = sinon.stub(MySQL.prototype, 'call') - .returns([{ Field: 'foo', extra: 1 }]); - - const fields = await mysql.getFields(dummyModel); - - assert.deepEqual(fields, { - foo: { Field: 'foo', extra: 1 } - }); - - assert.equal(stubCall.args[0][0], `SHOW COLUMNS FROM ${fullTableName}`); - - stubCall.restore(); - }); - }); - describe('save methods', function() { let stubFields; @@ -278,6 +202,7 @@ describe('MySQL', function() { assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); assert.deepEqual(stubCall.args[0][1], { set_foo: 'bar', foo: 'barr' }); }); + }); describe('should throw', function() { @@ -293,6 +218,82 @@ describe('MySQL', function() { }); }); + describe('get() getTotals()', function() { + + const stubExecute = result => sinon.stub(QueryBuilder.prototype, 'execute').callsFake(() => result); + + const testParams = (params, expectedParams) => { + assert.deepEqual(params, expectedParams, 'shouldn\'t modify ofiginal params'); + }; + + it('Should return empty results and totals with zero values', async function() { + + const params = {}; + + const stub = stubExecute([]); + + const result = await mysql.get(dummyModel, params); + + assert.deepEqual(result, []); + + const resultTotals = await mysql.getTotals(dummyModel); + + assert.deepEqual(resultTotals, { total: 0, pages: 0 }); + + testParams(params, {}); + + stub.restore(); + }); + + it('Should return results and totals', async function() { + + const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; + const params = { ...originalParams }; + + const stubResults = stubExecute([{ result: 1 }, { result: 2 }]); + + const result = await mysql.get(dummyModel, params); + + assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); + + testParams(params, originalParams); + + stubResults.restore(); + + const stubTotals = stubExecute([{ count: 650 }]); + + const resultTotals = await mysql.getTotals(dummyModel); + + assert.deepEqual(resultTotals, { + total: 650, + page: 4, + pageSize: 10, + pages: 65 + }); + + stubTotals.restore(); + }); + }); + + describe('getFields()', function() { + + it('Should return formatted fields', async function() { + + const stubCall = sinon.stub(MySQL.prototype, 'call') + .returns([{ Field: 'foo', extra: 1 }]); + + const fields = await mysql.getFields(dummyModel); + + assert.deepEqual(fields, { + foo: { Field: 'foo', extra: 1 } + }); + + assert.equal(stubCall.args[0][0], `SHOW COLUMNS FROM ${fullTableName}`); + + stubCall.restore(); + }); + }); + describe('remove methods', function() { let stubFields; @@ -553,4 +554,35 @@ describe('MySQL', function() { }); }); + describe('GetConnection', () => { + + const fakeConnection = { + config: {} + }; + + const fakePoolWithError = { + getConnection: cb => cb(new Error('some database error'), null) + }; + + const fakePoolValid = { + getConnection: cb => cb(null, fakeConnection) + }; + + it('should get Error when connection is not working', async() => { + + const stubPool = sinon.stub(MySQL.prototype, 'pool').get(() => fakePoolWithError); + await assert.rejects(mysql.getConnection(), { message: 'some database error' }); + stubPool.restore(); + }); + + it('should connect and add QueryFormat', async() => { + const stubPool = sinon.stub(MySQL.prototype, 'pool').get(() => fakePoolValid); + fakeConnection.config.queryFormat = MySQL.queryFormat; + + await assert(typeof mysql.getConnection(), 'object'); + await assert.deepEqual(await mysql.getConnection(), fakeConnection); + stubPool.restore(); + }); + + }); }); From 655723189d117982bfbe9fd9d7e151af66add919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Pereyra?= Date: Tue, 4 Jun 2019 10:14:28 -0300 Subject: [PATCH 05/35] Fixed MySQLError --- mysql/mysql-error.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mysql/mysql-error.js b/mysql/mysql-error.js index ddc53f2..b65ca12 100644 --- a/mysql/mysql-error.js +++ b/mysql/mysql-error.js @@ -7,14 +7,18 @@ class MySQLError extends Error { return { EMPTY_FIELDS: 1, INVALID_STATEMENT: 2, - INVALID_DATA: 3 + INVALID_DATA: 3, + TOO_MANY_CONNECTION: 4, + CONNECTION_ERROR: 5, + INVALID_QUERY }; } - constructor(err) { + constructor(err, code) { super(err); this.message = err.message || err; + this.code = code; this.name = 'MySQLError'; } } From b71ea6c7be12ed401a711338680a240a0a277990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Pereyra?= Date: Tue, 4 Jun 2019 18:20:14 -0300 Subject: [PATCH 06/35] Convert MySQL module to used mysql2 promises --- mysql/mysql-error.js | 2 +- mysql/mysql-promise.js | 163 ---------- mysql/mysql.js | 683 ++++++++++++++++++++--------------------- tests/mysql-test.js | 169 +++++++--- 4 files changed, 461 insertions(+), 556 deletions(-) delete mode 100644 mysql/mysql-promise.js diff --git a/mysql/mysql-error.js b/mysql/mysql-error.js index b65ca12..2c8a426 100644 --- a/mysql/mysql-error.js +++ b/mysql/mysql-error.js @@ -10,7 +10,7 @@ class MySQLError extends Error { INVALID_DATA: 3, TOO_MANY_CONNECTION: 4, CONNECTION_ERROR: 5, - INVALID_QUERY + INVALID_QUERY: 6 }; } diff --git a/mysql/mysql-promise.js b/mysql/mysql-promise.js deleted file mode 100644 index 0c26657..0000000 --- a/mysql/mysql-promise.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -const mysql = require('mysql2/promise'); -const knex = require('knex'); - -const logger = require('@janiscommerce/logger'); -const QueryBuilder = require('@janiscommerce/query-builder'); - -const MySQLError = require('./mysql-error'); - -const Utils = require('./../utils'); - -const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds -const CONNECTION_LIMIT = 10; - -const DEFAULT_LIMIT = 500; - -class MySQL { - - static get defaultLimit() { - return DEFAULT_LIMIT; - } - - static get maxIddleTimeout() { - return MAX_IDDLE_TIMEOUT; - } - - static get filters() { - return '_filters'; - } - - static get columns() { - return '_columns'; - } - - static get joins() { - return '_joins'; - } - - /** - * @returns {object} connection pool - */ - static get connectionPool() { - return this._connectionPool || {}; - } - - /** - * Set a connection pool - * @param {object} connection Pool connection data - */ - static set connectionPool(connection) { - - if(!this._connectionPool) - this._connectionPool = {}; - - this._connectionPool[connection.threadId] = { - connection, - lastActivity: (new Date() / 1000 | 0), - id: connection.threadId - }; - } - - /** - * Constructor - * @param {object} config Database configuration. - */ - constructor(config) { - this.config = { - host: config.host, - user: config.user, - password: config.password, - database: config.database || null, - port: config.port, - connectionLimit: config.connectionLimit || CONNECTION_LIMIT, - multipleStatements: true, - prefix: config.prefix || '' - }; - } - - /** - * Returns a MySQL pool, if it didn't exist create one. - * @returns {object} MySQL pool instance - */ - get pool() { - - if(!this._pool) - this._pool = mysql.createPool(this.config); - - return this._pool; - } - - /* istanbul ignore next */ - _queryFormat(query, values) { - if(!values) - return query; - - return query.replace(/:(\w+)/g, (txt, key) => { - if(values.hasOwnProperty(key)) - return mysql.escape(values[key]); - - return txt; - }); - } - - /** - * Async - * @returns {Promise} MySQL connection - */ - getConnection() { - return new Promise(async(resolve, reject) => { - - try { - - const { connection } = await this.pool.getConnection(); - connection.config.queryFormat = this._queryFormat.bind(this); - this.constructor.connectionPool = connection; - resolve(await this.pool.getConnection()); - - } catch(error) { - reject(error); - } - }); - } - - /** - * Execute de Query in the database - * @param {String} query Query to be executed - * @param {*} placeholders - */ - _call(query, placeholders = {}) { - return new Promise(async(resolve, reject) => { - try { - const connection = await this.getConnection(); - const rowsAffected = await connection.query(query, placeholders); - connection.release(); - resolve(rowsAffected); - /* connection.query(query, placeholders, (err, rows) => { - - if(err) { - logger.error('query', err.errno, err.code, err.message); - logger.debug(query, placeholders); - reject(err); - } - - connection.release(); - resolve(rows); - }); */ - - } catch(error) { - if(error.code === 'ER_CON_COUNT_ERROR') // Too Many Connections - return setTimeout(() => this._call.apply(this, arguments), 500); // Retry - - logger.error('Database', error.message); - reject(error); - - } - }); - } - - -} - -module.exports = MySQL; diff --git a/mysql/mysql.js b/mysql/mysql.js index 81f7029..8eac138 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -1,13 +1,13 @@ 'use strict'; -const mysql = require('mysql2'); +const mysql = require('mysql2/promise'); +const { escape } = require('mysql2'); const knex = require('knex'); const logger = require('@janiscommerce/logger'); const QueryBuilder = require('@janiscommerce/query-builder'); const MySQLError = require('./mysql-error'); - const Utils = require('./../utils'); const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds @@ -100,75 +100,160 @@ class MySQL { wrapIdentifier: (value, origImpl) => origImpl(Utils.convertToSnakeCase(value)), postProcessResponse: result => (Array.isArray(result) ? result.map(Utils.convertKeysToCamelCase) : Utils.convertKeysToCamelCase(result)) }).on('query-error', error => { - logger.error('query', error); + logger.error('Query', error); }); } return this._knex; } + /* istanbul ignore next */ + _queryFormat(query, values) { + if(!values) + return query; + + return query.replace(/:(\w+)/g, (txt, key) => { + if(values.hasOwnProperty(key)) + return escape(values[key]); + + return txt; + }); + } + /** - * Search in the database. - * - * @param {Class} model Model Class - * @param {object} params object with the parametres to search. - * @returns {Promise} Results of the search + * Get the pool connection. + * @returns {Promise} Pool Promise Connection or MySQLError */ - async get(model, params) { + getConnection() { + return new Promise(async(resolve, reject) => { - const newParams = { ...params }; // necesario para no alterar params y no afectar a las keys de cache + try { - if(!newParams.totals) { + const poolConnection = await this.pool.getConnection(); - if(!newParams.limit) - newParams.limit = this.constructor.defaultLimit; + poolConnection.connection.config.queryFormat = this._queryFormat.bind(this); + this.constructor.connectionPool = poolConnection.connection; - model.totalsParams = newParams; + resolve(poolConnection); - } else - delete newParams.totals; + } catch(error) { + if(error.code === 'ER_CON_COUNT_ERROR') + reject(new MySQLError(error.code, MySQLError.codes.TOO_MANY_CONNECTION)); - const queryBuilder = new QueryBuilder(this.knex, model, newParams); + reject(new MySQLError(error.code, MySQLError.codes.CONNECTION_ERROR)); + } + }); + } - queryBuilder.build(); + /** + * Map a field to it's DB column name + * @param {string} field - The field that will be mapped. + * @param {object} [map=this.fieldsMap] - The map + * @private + */ + _mapField(field, map) { + if(typeof field === 'symbol') + return field; - const result = await queryBuilder.execute(); + let parsed = null; - model.lastQueryEmpty = !result.length; + Object.entries(map || this.fieldsMap || {}).forEach(([key, value]) => { - return result; - } + value = Array.isArray(value) ? value : [value]; - async getTotals(model) { + if(value.includes(field)) + parsed = key; + }); - if(model.lastQueryEmpty) - return { total: 0, pages: 0 }; + if(!parsed) { + // Replace camel case to lower dash format. + // E.g: camelCase => camel_case; + // UpperCamelCase => upper_camel_case - const params = model.totalsParams || {}; + parsed = field.replace(/[A-Z]/g, (match, index) => (index !== 0 ? '_' : '') + match.toLowerCase()); + } - const [result] = await this.get(model, { - ...params, - totals: true, - fields: false, - count: true, - page: 1, - limit: 1 + return parsed; + } + + /** + * Map each property to DB column name + * @param {object} item - The object that will be mapped + * @param {object} [map=this.fieldsMap] - The map + * @private + */ + _mapItem(item, map) { + const fields = {}; + item = typeof item === 'undefined' ? {} : item; + + // We use Reflect and not Object.entries/Keys + // because we want to keep Symbol properties + Reflect.ownKeys(item).forEach(key => { + const field = this._mapField(key, map); + fields[field || key] = item[key]; }); - return { - total: result.count, - page: params.page ? params.page : 1, - pageSize: params.limit ? params.limit : this.constructor.defaultLimit, - pages: params.limit ? Math.ceil(result.count / params.limit) : 1 - }; + return fields; + } + + /** + * Map each propety in the collection to a valid DB collection + * @param {object/array} data - An array of objects, or an object. + * @param {object} [map=this.fieldsMap] - The map + * + */ + _mapFields(data, map) { + if(Array.isArray(data)) + return data.map(item => this._mapItem(item, map)); + + return this._mapItem(data, map); + } - async getFields(model) { + /** + * Execute de Query in the database + * @param {string} query Query to be executed, placeholders with :PARAM_NAME + * @param {object} placeholders Object with key PARAM_NAME, value PARAM_VALUE, Default empty + * @returns {Promise} Resolve: RowsAffected, Reject: MySQLError + */ + _call(query, placeholders = {}) { + return new Promise(async(resolve, reject) => { + try { + const connection = await this.getConnection(); + const rows = await connection.query(query, placeholders); + + connection.release(); + + resolve(rows); + + } catch(error) { + // Connections Limit + if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) + return setTimeout(() => this._call.apply(this, arguments), 500); // Retry + // Other Connections Errors + if(error.code === MySQLError.codes.CONNECTION_ERROR) { + logger.error('Database', error.message); + reject(error); + } + // Query Errors + logger.error('Query', error.errno, error.code, error.message); + logger.debug(query, placeholders); + reject(new MySQLError(error.code, MySQLError.codes.INVALID_QUERY)); + } + }); + } + + /** + * Get the Fields from the model + * @param {class} model Model Class + */ + async _getFields(model) { const table = model.getTable(); const { dbname } = model; - const rows = await this.call(`SHOW COLUMNS FROM ${dbname}.${table}`); + // _call returns an array with colummns in the first position and Schema information in the second + const [rows] = await this._call(`SHOW COLUMNS FROM ${dbname}.${table}`); const fields = {}; @@ -179,23 +264,133 @@ class MySQL { } /** - * Save an item in the SQL database - * @param {Class} model Model Class - * @param {object} item object to saved - * @returns {number} Rows affected + * Generates joins + * @param {Array.<{table: String, type: String, alias: String, condition: String}>} joins - Array of shape [{ table, type, alias, condition }] + * @returns {string} */ - async save(model, item) { - return this.insert(model, item, true); + _buildJoins(joins) { + if(!joins || !Array.isArray(joins)) + return ''; + + return joins.reduce((acc, { table, type = 'LEFT', alias, condition }) => { + if(!/^(LEFT|RIGHT|INNER)$/.test(type)) + return acc; + + return `${acc} ${type} JOIN ${this.dbname}.${table} ${alias} ON ${condition}`; + }, ''); } /** - * Insert/update a row + * Generate where and placeholders for a single field + * + * @param {string} field - The field + * @param {mixed} value - The field's value + * @return {object} { where, placeholders } + */ + static _buildFieldQuery(field, value, alias = '', suffix = '') { + + const where = []; + const placeholders = {}; + + const fieldKey = `${alias ? alias + '.' : ''}${field}`; + + if(value instanceof Array) { + const queryIn = []; + + const hasNull = value.some(item => item === null); + + if(hasNull) + value = value.filter(item => item !== null); + + for(let i = 0; i < value.length; i++) { + + const key = `${field}_${i}${suffix}`; + + placeholders[key] = value[i]; + queryIn.push(':' + key); + + } + + if(queryIn.length) { + + if(hasNull) + where.push(`(${fieldKey} IS NULL OR ${fieldKey} IN (${queryIn.join(',')}))`); + else where.push(`${fieldKey} IN (${queryIn.join(',')})`); + } + + + } else { + + const key = `${field}${suffix}`; + + placeholders[key] = value; + + if(value === null) + where.push(`${fieldKey} IS NULL`); + else where.push(`${fieldKey} = :${key}`); + } + + return { where, placeholders }; + + } + + /** + * Generate Where and placeholders to query with fields + * + * @param {class} Model Class + * @param {object} fields The fields + * @param {string} tableAlias + * @param {string} suffix + * @return {object} { where and placeholders } + */ + async _prepareFields(model, fields = {}, tableAlias = '', suffix = '') { + let where = []; + const placeholders = {}; + + const columns = fields[this.constructor.columns] || ['*']; + + const joins = this._buildJoins(fields[this.constructor.joins]); + + if(!Object.keys(fields).length) { + return { + where, + placeholders, + columns, + joins + }; + } + + const tableFields = await this._getFields(model); + + fields = this._mapFields(fields); + + for(const [field, value] of Object.entries(fields)) { + + if(!tableFields[field] || value === undefined) + continue; + + const { where: w, placeholders: ph } = this.constructor._buildFieldQuery(field, value, tableAlias, suffix); + + where = [...where, ...w]; + + Object.assign(placeholders, ph); + } + + return { + where, + placeholders, + columns, + joins + }; + } + + /** + * Insert an item into the database * @param {Class} model - Model Class * @param {object} item - The item to insert * @param {boolean} allowUpsert - If upsert is allowed - * @returns {number} Rows affected + * @returns {number} Number of the ID inserted */ - async insert(model, item, allowUpsert = false) { const table = model.getTable(); @@ -208,9 +403,9 @@ class MySQL { const time = (Date.now() / 1000 | 0); - const tableFields = await this.getFields(model); + const tableFields = await this._getFields(model); - item = this.mapFields(item); + item = this._mapFields(item); if(!Object.keys(item).some(field => typeof tableFields[field] !== 'undefined')) { return Promise.reject( @@ -236,27 +431,26 @@ class MySQL { placeholders[key] = key === 'date_modified' ? time : value; - if(!noUpdate.includes(key)) duplicateUpdate.push(`${key} = VALUES(${key})`); }); const duplicateStatement = `ON DUPLICATE KEY UPDATE - ${duplicateUpdate.join(',\n')}`; + ${duplicateUpdate.join(',\n')}`; const statement = `INSERT INTO ${dbname}.${table} (${fields.join(', ')}) - VALUES (${fields.map(field => ':' + field).join(', ')}) - ${allowUpsert ? duplicateStatement : ''}`; + VALUES (${fields.map(field => ':' + field).join(', ')}) + ${allowUpsert ? duplicateStatement : ''}`; - const rows = await this.call(statement, placeholders); + const [rows] = await this._call(statement, placeholders); return rows.insertId; } /** * Update a row - * @param {object} item - The date to update + * @param {object} item - The item to update */ async update(model, item) { @@ -265,20 +459,20 @@ class MySQL { const placeholders = {}; - const tableFields = await this.getFields(model); + const tableFields = await this._getFields(model); const fields = []; let where = []; let wherePlaceholders = {}; - item = this.mapFields(item); + item = this._mapFields(item); if(item[this.constructor.filters]) { const whereField = item[this.constructor.filters]; - ({ where, placeholders: wherePlaceholders } = await this.prepareFields(model, whereField)); + ({ where, placeholders: wherePlaceholders } = await this._prepareFields(model, whereField)); Object.assign(placeholders, wherePlaceholders); } @@ -303,7 +497,7 @@ class MySQL { if(!fields.length) { return Promise.reject( - new MySQLError('Insert must have fields', MySQLError.codes.EMPTY_FIELDS) + new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS) ); } @@ -311,105 +505,89 @@ class MySQL { const statement = `UPDATE ${dbname}.${table} SET ${fields.join(', ')} ${clause}`; - return this.call(statement, placeholders); - } + const [rows] = await this._call(statement, placeholders); - call(query, placeholders = {}) { - return new Promise(async(resolve, reject) => { - try { - const connection = await this.getConnection(); - connection.query(query, placeholders, (err, rows) => { - - if(err) { - logger.error('query', err.errno, err.code, err.message); - logger.debug(query, placeholders); - reject(err); - } - - connection.release(); - resolve(rows); - }); - - } catch(error) { - if(error.code === 'ER_CON_COUNT_ERROR') // Too Many Connections - return setTimeout(() => this.call.apply(this, arguments), 500); // Retry - - logger.error('Database', error); - reject(error); - - } - /* - this.query(query, placeholders, (err, rows) => { - if(err) - reject(err); - - resolve(rows); - }); */ - }); + return rows.insertId; } /** - * Async - * @returns {Promise} MySQL connection + * Save a new item in the SQL database + * @param {Class} model Model Class + * @param {object} item object to saved + * @returns {number} Number of the ID of the item */ - getConnection() { - return new Promise((resolve, reject) => { - - this.pool.getConnection((err, connection) => { - if(err) - reject(err); - - connection.config.queryFormat = this.queryFormat.bind(this); + async save(model, item) { + return this.insert(model, item, true); + } - this.constructor.connectionPool = connection; - resolve(connection); - }); + /** + * Search in the database. + * + * @param {Class} model Model Class + * @param {object} params object with the parametres to search. + * @returns {Promise} Array with the objects founds. + */ + async get(model, params = {}) { - }); - } + const newParams = { ...params }; // necesario para no alterar params y no afectar a las keys de cache -/* - query(statement, placeholders, callback) { + if(!newParams.totals) { - this.pool.getConnection((connErr, connection) => { + if(!newParams.limit) + newParams.limit = this.constructor.defaultLimit; - if(connErr) { + model.totalsParams = newParams; - if(connErr.code === 'ER_CON_COUNT_ERROR') // TOO MANY CONNECTIONS - return setTimeout(() => this.query.apply(this, arguments), 500); // Retry + } else + delete newParams.totals; - if(typeof callback === 'function') - callback(connErr); + const queryBuilder = new QueryBuilder(this.knex, model, newParams); - logger.error('Database', connErr); + queryBuilder.build(); - return; - } + const result = await queryBuilder.execute(); - connection.config.queryFormat = this.queryFormat.bind(this); + model.lastQueryEmpty = !result.length; - this.constructor.connectionPool = connection; + return result; + } - return connection.query(statement, placeholders, (err, rows) => { + /** + * Get the stadistics + * @param {class} model Model Class + * @returns {object} return Total of values, actual Page, Page Size and Total Pages + */ + async getTotals(model) { - if(err) { - logger.error('query', err.errno, err.code, err.message); - logger.debug(statement, placeholders); - } + if(model.lastQueryEmpty) + return { total: 0, pages: 0 }; - if(typeof callback === 'function') - callback(err, rows); + const params = model.totalsParams || {}; - connection.release(); - }); + const [result] = await this.get(model, { + ...params, + totals: true, + fields: false, + count: true, + page: 1, + limit: 1 }); + + return { + total: result.count, + page: params.page ? params.page : 1, + pageSize: params.limit ? params.limit : this.constructor.defaultLimit, + pages: params.limit ? Math.ceil(result.count / params.limit) : 1 + }; } - */ + /** * Perform a multi insert + * @param {class} model Model Class * @param {array} items - The items to insert - * @param {string} [table=this.constructor.table] - The table + * @returns {boolean} True if success */ + /* istanbul ignore next */ async multiInsert(model, items) { if(!items || !items.length) { @@ -428,7 +606,7 @@ class MySQL { let fields; - const tableFields = await this.getFields(model); + const tableFields = await this._getFields(model); if(tableFields.id) duplicateUpdate.push('id = LAST_INSERT_ID(id)'); @@ -439,7 +617,7 @@ class MySQL { const itemValues = []; - items[i] = this.mapFields(items[i]); + items[i] = this._mapFields(items[i]); if(tableFields.date_created) items[i].date_created = items[i].date_created || time; @@ -479,7 +657,7 @@ class MySQL { ON DUPLICATE KEY UPDATE ${duplicateUpdate.join(',\n')}`; - const rows = await this.call(statement, placeholders); + const [rows] = await this._call(statement, placeholders); return !!rows.insertId; } @@ -488,7 +666,7 @@ class MySQL { * Remove by fields and value * * @param {object} model The model - * @param {object} fields The fields to use in where clause + * @param {object} fields The fields to use in where clause, invalid fields will be ignore * @return {Promise} { response from database } */ async remove(model, fields) { @@ -507,7 +685,7 @@ class MySQL { placeholders, where, joins - } = await this.prepareFields(model, fields); + } = await this._prepareFields(model, fields); const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : ''; @@ -517,198 +695,13 @@ class MySQL { ${whereClause} ${joinsClause}`; - return this.call(statement, placeholders); + return this._call(statement, placeholders); } /** - * Generate Where and placeholders to query with fields - * - * @param {object} fields The fields - * @param {string} table The table - * @return {object} { where and placeholders } + * Ends connections + * @returns {Promise} If error, else nothing */ - - async prepareFields(model, fields, tableAlias = '', suffix = '') { - - let where = []; - const placeholders = {}; - - const columns = fields[this.constructor.columns] || ['*']; - - const joins = this.buildJoins(fields[this.constructor.joins]); - - if(!Object.keys(fields).length) { - return { - where, - placeholders, - columns, - joins - }; - } - - const tableFields = await this.getFields(model); - - fields = this.mapFields(fields); - - for(const [field, value] of Object.entries(fields)) { - - if(!tableFields[field] || value === undefined) - continue; - - const { where: w, placeholders: ph } = this.constructor.buildFieldQuery(field, value, tableAlias, suffix); - - where = [...where, ...w]; - - Object.assign(placeholders, ph); - } - - return { - where, - placeholders, - columns, - joins - }; - } - - /** - * Generates joins - * @param {Array.<{table: String, type: String, alias: String, condition: String}>} joins - Array of shape [{ table, type, alias, condition }] - * @returns {string} - */ - - buildJoins(joins) { - if(!joins || !Array.isArray(joins)) - return ''; - - return joins.reduce((acc, { table, type = 'LEFT', alias, condition }) => { - if(!/^(LEFT|RIGHT|INNER)$/.test(type)) - return acc; - - return `${acc} ${type} JOIN ${this.dbname}.${table} ${alias} ON ${condition}`; - }, ''); - } - - /** - * Generate where and placeholders for a single field - * - * @param {string} field - The field - * @param {mixed} value - The field's value - * @return {object} { where, placeholders } - */ - - static buildFieldQuery(field, value, alias = '', suffix = '') { - - const where = []; - const placeholders = {}; - - const fieldKey = `${alias ? alias + '.' : ''}${field}`; - - if(value instanceof Array) { - const queryIn = []; - - const hasNull = value.some(item => item === null); - - if(hasNull) - value = value.filter(item => item !== null); - - for(let i = 0; i < value.length; i++) { - - const key = `${field}_${i}${suffix}`; - - placeholders[key] = value[i]; - queryIn.push(':' + key); - - } - - if(queryIn.length) { - - if(hasNull) - where.push(`(${fieldKey} IS NULL OR ${fieldKey} IN (${queryIn.join(',')}))`); - else where.push(`${fieldKey} IN (${queryIn.join(',')})`); - } - - - } else { - - const key = `${field}${suffix}`; - - placeholders[key] = value; - - if(value === null) - where.push(`${fieldKey} IS NULL`); - else where.push(`${fieldKey} = :${key}`); - } - - return { where, placeholders }; - - } - - /** - * Map each propety in the collection to a valid DB collection - * @param {object/array} data - An array of objects, or an object. - * @param {object} [map=this.fieldsMap] - The map - * - */ - - mapFields(data, map) { - if(Array.isArray(data)) - return data.map(item => this.mapItem(item, map)); - - return this.mapItem(data, map); - - } - - /** - * Map each property to DB column name - * @param {object} item - The object that will be mapped - * @param {object} [map=this.fieldsMap] - The map - * @private - */ - mapItem(item, map) { - const fields = {}; - - // We use Reflect and not Object.entries/Keys - // because we want to keep Symbol properties - Reflect.ownKeys(item).forEach(key => { - const field = this.mapField(key, map); - fields[field || key] = item[key]; - }); - - return fields; - } - - /** - * Map a field to it's DB column name - * @param {string} field - The field that will be mapped. - * @param {object} [map=this.fieldsMap] - The map - * @private - */ - - mapField(field, map) { - if(typeof field === 'symbol') - return field; - - let parsed = null; - - /* Object.entries(map || this.fieldsMap || {}).forEach(([key, value]) => { - - value = Array.isArray(value) ? value : [value]; - - if(value.includes(field)) - parsed = key; - }); */ - - if(!parsed) { - // Replace camel case to lower dash format. - // E.g: camelCase => camel_case; - // UpperCamelCase => upper_camel_case - - parsed = field.replace(/[A-Z]/g, (match, index) => (index !== 0 ? '_' : '') + match.toLowerCase()); - } - - return parsed; - } - /* istanbul ignore next */ end() { return new Promise((resolve, reject) => { @@ -730,25 +723,19 @@ class MySQL { }); } - /* istanbul ignore next */ - queryFormat(query, values) { - if(!values) - return query; - - return query.replace(/:(\w+)/g, (txt, key) => { - if(values.hasOwnProperty(key)) - return mysql.escape(values[key]); - - return txt; - }); - } - - shouldDestroyConnectionPool(lastActivity) { + /** + * Returns if the Connection is innactive + * @param {number} lastActivity Date of the last Activity + * @returns {boolean} + */ + _shouldDestroyConnectionPool(lastActivity) { return !lastActivity || ((Date.now() / 1000 | 0) - lastActivity > this.constructor.maxIddleTimeout); } - - /* istanbul ignore next */ + /** + * Check for Iddle Connections and ends it. + */ + /* istanbul ignore next */ closeIddleConnections() { if(this.closeIddleConnectionsInterval) @@ -763,7 +750,7 @@ class MySQL { .forEach(connectionPool => { if(connectionPool.connection && - this.shouldDestroyConnectionPool(connectionPool.lastActivity)) { + this._shouldDestroyConnectionPool(connectionPool.lastActivity)) { logger.info(`Destroying connection: ${connectionPool.id}`); @@ -787,10 +774,12 @@ class MySQL { /** * No need to create indexes in this Database */ - /* + /* istanbul ignore next */ async createIndexes() { return true; - } */ + } + + } module.exports = MySQL; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 7a4bfd2..6f004e5 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -122,11 +122,11 @@ describe('MySQL', function() { ['id', 'foo', 'date_created', 'date_modified'].forEach(field => { fields[field] = field; }); fields.date_test = { Field: 'date_test', Type: 'datetime' }; - stubFields = sinon.stub(MySQL.prototype, 'getFields') + stubFields = sinon.stub(MySQL.prototype, '_getFields') .returns(fields); - stubCall = sinon.stub(MySQL.prototype, 'call') - .returns({ insertId }); + stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([{ insertId }]); }); afterEach(() => { @@ -279,10 +279,10 @@ describe('MySQL', function() { it('Should return formatted fields', async function() { - const stubCall = sinon.stub(MySQL.prototype, 'call') - .returns([{ Field: 'foo', extra: 1 }]); + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo', extra: 1 }]]); - const fields = await mysql.getFields(dummyModel); + const fields = await mysql._getFields(dummyModel); assert.deepEqual(fields, { foo: { Field: 'foo', extra: 1 } @@ -304,10 +304,10 @@ describe('MySQL', function() { const fields = {}; ['id', 'foo', 'date_created', 'date_modified'].forEach(field => { fields[field] = field; }); - stubFields = sinon.stub(MySQL.prototype, 'getFields') + stubFields = sinon.stub(MySQL.prototype, '_getFields') .returns(fields); - stubCall = sinon.stub(MySQL.prototype, 'call') + stubCall = sinon.stub(MySQL.prototype, '_call') .returns(true); }); @@ -344,39 +344,20 @@ describe('MySQL', function() { assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); }); - }); - - describe('shouldDestroyConnectionPool', function() { - - it('should return true', function() { - - const now = Date.now() / 1000 | 0; - - const dates = [ - now - MySQL.maxIddleTimeout - 50 // 50 seconds after iddle timeout limit - ]; - dates.forEach(lastActivity => { - assert(mysql.shouldDestroyConnectionPool(lastActivity)); + it('should remove when valids fields given but some invalid too', async() => { + await mysql.remove(dummyModel, { + foo: 'bar', + w: true }); - assert(mysql.shouldDestroyConnectionPool()); - }); - - it('should return false', function() { - - const now = Date.now() / 1000 | 0; - - const dates = [ - now - MySQL.maxIddleTimeout + 50 // 50 seconds before iddle timeout limit - ]; + const expectedQuery = sanitizeQuery(` + DELETE FROM ${fullTableName} + WHERE foo = :foo`); - dates.forEach(lastActivity => { - assert(!mysql.shouldDestroyConnectionPool(lastActivity)); - }); + assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); }); - }); describe('Build field query', function() { @@ -386,7 +367,7 @@ describe('MySQL', function() { const values = [['foo', 'test'], ['bar', 1]]; for(const [field, value] of values) { - const rows = MySQL.buildFieldQuery(field, value); + const rows = MySQL._buildFieldQuery(field, value); assert.deepEqual(rows.where, [`${field} = :${field}`]); assert.deepEqual(rows.placeholders, { [field]: value }); @@ -400,7 +381,7 @@ describe('MySQL', function() { let index = 0; for(const [field, value] of values) { - const rows = MySQL.buildFieldQuery(field, value, '', '_' + index); + const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); const ph = `${field}_${index}`; assert.deepEqual(rows.where, [`${field} = :${ph}`]); @@ -417,7 +398,7 @@ describe('MySQL', function() { let index = 0; for(const [field, value] of values) { - const rows = MySQL.buildFieldQuery(field, value, '', '_' + index); + const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); const ph = `${field}_${index}`; assert.deepEqual(rows.where, [`${field} IS NULL`]); @@ -430,7 +411,7 @@ describe('MySQL', function() { it('Should build field query correctly for field with multi values', function() { - const res = MySQL.buildFieldQuery('foo', [1, '3']); + const res = MySQL._buildFieldQuery('foo', [1, '3']); assert.deepEqual(res.where, ['foo IN (:foo_0,:foo_1)']); assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); @@ -439,7 +420,7 @@ describe('MySQL', function() { it('Should build field query correctly for field with multi values including NULL', function() { - const res = MySQL.buildFieldQuery('foo', [1, '3', null]); + const res = MySQL._buildFieldQuery('foo', [1, '3', null]); assert.deepEqual(res.where, ['(foo IS NULL OR foo IN (:foo_0,:foo_1))']); assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); @@ -473,7 +454,7 @@ describe('MySQL', function() { }; - const result = mysql.mapFields(data, fieldsMap); + const result = mysql._mapFields(data, fieldsMap); assert.equal(result.orderFormId, undefined); assert.equal(result.checkBoolean, undefined); @@ -514,7 +495,7 @@ describe('MySQL', function() { checkString: 'string' }]; - const results = mysql.mapFields(data, fieldsMap); + const results = mysql._mapFields(data, fieldsMap); results.forEach(result => { assert.equal(result.orderFormId, undefined); @@ -544,7 +525,7 @@ describe('MySQL', function() { nomap: 5 }]; - const results = mysql.mapFields(data, fieldsMap); + const results = mysql._mapFields(data, fieldsMap); results.forEach(result => { assert.equal(result[Symbol.for('mock')], 'symbol'); @@ -554,7 +535,105 @@ describe('MySQL', function() { }); }); - describe('GetConnection', () => { + describe('shouldDestroyConnectionPool', function() { + + it('should return true', function() { + + const now = Date.now() / 1000 | 0; + + const dates = [ + now - MySQL.maxIddleTimeout - 50 // 50 seconds after iddle timeout limit + ]; + + dates.forEach(lastActivity => { + assert(mysql._shouldDestroyConnectionPool(lastActivity)); + }); + + assert(mysql._shouldDestroyConnectionPool()); + }); + + it('should return false', function() { + + const now = Date.now() / 1000 | 0; + + const dates = [ + now - MySQL.maxIddleTimeout + 50 // 50 seconds before iddle timeout limit + ]; + + dates.forEach(lastActivity => { + assert(!mysql._shouldDestroyConnectionPool(lastActivity)); + }); + + }); + + }); + + describe('End Connection', function() { + it('should end the connection', function() { + //mysql.end(); + //mysql.closeIddleConnections(); + }) + }); + + describe('Prepare Fields', function() { + + const expectedPrepareFieldsEmpty = { + where: [], + placeholders: {}, + columns: ['*'], + joins: '' + }; + + const expectedPrepareFields = { + where: ['foo = :foo'], + placeholders: { foo: 'bar' }, + columns: ['*'], + joins: '' + }; + + it.only('should return empty fields with no model or fields', async() => { + + const preparedFields = await mysql._prepareFields(); + assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + + }); + + it.only('should return empty fields with no fields', async() => { + + const preparedFields = await mysql._prepareFields(dummyModel); + assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + + }); + + it.only('should return the correct where and placeholders fields', async() => { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo' }]]); + + const preparedFields = await mysql._prepareFields(dummyModel, { foo: 'bar' }); + + assert.deepEqual(preparedFields, expectedPrepareFields); + + stubCall.restore(); + + }); + + it.only('should return empty fields with no fields', async() => { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo' }]]); + + const preparedFields = await mysql._prepareFields(dummyModel, {foo: 'bar_t'}); + + //console.log(preparedFields); + //assert.deepEqual(preparedFields, expectedPrepareFields); + + stubCall.restore(); + + }); + }); + + /* describe('GetConnection', () => { const fakeConnection = { config: {} @@ -584,5 +663,5 @@ describe('MySQL', function() { stubPool.restore(); }); - }); + }); */ }); From b7820536255267d9a3965b12beeae17af1a9e93b Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Wed, 5 Jun 2019 18:34:19 -0300 Subject: [PATCH 07/35] Fixed Mysql module --- mysql/mysql.js | 60 +++++++------- tests/mysql-test.js | 193 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 191 insertions(+), 62 deletions(-) diff --git a/mysql/mysql.js b/mysql/mysql.js index 8eac138..73337fc 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -130,13 +130,13 @@ class MySQL { try { const poolConnection = await this.pool.getConnection(); - poolConnection.connection.config.queryFormat = this._queryFormat.bind(this); this.constructor.connectionPool = poolConnection.connection; resolve(poolConnection); } catch(error) { + if(error.code === 'ER_CON_COUNT_ERROR') reject(new MySQLError(error.code, MySQLError.codes.TOO_MANY_CONNECTION)); @@ -216,31 +216,35 @@ class MySQL { * @param {object} placeholders Object with key PARAM_NAME, value PARAM_VALUE, Default empty * @returns {Promise} Resolve: RowsAffected, Reject: MySQLError */ - _call(query, placeholders = {}) { - return new Promise(async(resolve, reject) => { - try { - const connection = await this.getConnection(); - const rows = await connection.query(query, placeholders); + async _call(query, placeholders = {}) { - connection.release(); + try { - resolve(rows); + const connection = await this.getConnection(); + const rows = await connection.query(query, placeholders); + connection.release(); - } catch(error) { - // Connections Limit - if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) - return setTimeout(() => this._call.apply(this, arguments), 500); // Retry - // Other Connections Errors - if(error.code === MySQLError.codes.CONNECTION_ERROR) { - logger.error('Database', error.message); - reject(error); - } - // Query Errors - logger.error('Query', error.errno, error.code, error.message); - logger.debug(query, placeholders); - reject(new MySQLError(error.code, MySQLError.codes.INVALID_QUERY)); + return rows; + + } catch(error) { + + // Connections Limit + if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) { + // Retry + let retryFunction; + setTimeout(retryFunction = () => this._call.apply(this, arguments), 500); + return retryFunction(); } - }); + // Other Connections Errors + if(error.code === MySQLError.codes.CONNECTION_ERROR) { + logger.error('Database', error.message); + return error; + } + // Query Errors + logger.error('Query', error.errno, error.code, error.message); + logger.debug(query, placeholders); + return new MySQLError(error.code, MySQLError.codes.INVALID_QUERY); + } } /** @@ -264,11 +268,12 @@ class MySQL { } /** - * Generates joins + * Generates joins string for queries * @param {Array.<{table: String, type: String, alias: String, condition: String}>} joins - Array of shape [{ table, type, alias, condition }] + * @param {string} dbname Database Name * @returns {string} */ - _buildJoins(joins) { + _buildJoins(joins, dbname) { if(!joins || !Array.isArray(joins)) return ''; @@ -276,7 +281,7 @@ class MySQL { if(!/^(LEFT|RIGHT|INNER)$/.test(type)) return acc; - return `${acc} ${type} JOIN ${this.dbname}.${table} ${alias} ON ${condition}`; + return `${acc} ${type} JOIN ${dbname}.${table} ${alias} ON ${condition}`; }, ''); } @@ -343,13 +348,14 @@ class MySQL { * @param {string} suffix * @return {object} { where and placeholders } */ - async _prepareFields(model, fields = {}, tableAlias = '', suffix = '') { + async _prepareFields(model = {}, fields = {}, tableAlias = '', suffix = '') { let where = []; const placeholders = {}; + const { dbname } = model; const columns = fields[this.constructor.columns] || ['*']; - const joins = this._buildJoins(fields[this.constructor.joins]); + const joins = this._buildJoins(fields[this.constructor.joins], dbname); if(!Object.keys(fields).length) { return { diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 6f004e5..06bf30c 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -535,6 +535,115 @@ describe('MySQL', function() { }); }); + describe('Prepare Fields', function() { + + const expectedPrepareFieldsEmpty = { + where: [], + placeholders: {}, + columns: ['*'], + joins: '' + }; + + const expectedPrepareFields = { + where: ['foo = :foo'], + placeholders: { foo: 'bar' }, + columns: ['*'], + joins: '' + }; + + it('should return empty fields with no model or fields', async() => { + + const preparedFields = await mysql._prepareFields(); + assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + + }); + + it('should return empty fields with no fields', async() => { + + const preparedFields = await mysql._prepareFields(dummyModel); + assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + + }); + + it('should return the correct where and placeholders fields', async() => { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo' }]]); + + const preparedFields = await mysql._prepareFields(dummyModel, { foo: 'bar' }); + + assert.deepEqual(preparedFields, expectedPrepareFields); + + stubCall.restore(); + + }); + + it('should return LEFT JOIN query if Join Type field not exist', async() => { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo' }]]); + + const fakeFields = { + foo: 'bar', + _joins: [ + { table: 'table_b', alias: 'tb', condition: 'foo' } + ] + }; + + const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + expectedPrepareFields.joins = ' LEFT JOIN dbname.table_b tb ON foo'; + + assert.deepEqual(preparedFields, expectedPrepareFields); + + expectedPrepareFields.joins = ''; + stubCall.restore(); + + }); + + it('should return empty join string if join type is \'\'', async() => { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo' }]]); + + const fakeFields = { + foo: 'bar', + _joins: [ + { table: 'table_b', type: '', alias: 'tb', condition: 'foo' } + ] + }; + + const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + + assert.deepEqual(preparedFields, expectedPrepareFields); + + expectedPrepareFields.joins = ''; + stubCall.restore(); + + }); + + it('should return RIGHT JOIN query with join type is \'RIGHT\' ', async() => { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo' }]]); + + const fakeFields = { + foo: 'bar', + _joins: [ + { table: 'table_b', type: 'RIGHT', alias: 'tb', condition: 'foo' } + ] + }; + + const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + expectedPrepareFields.joins = ' RIGHT JOIN dbname.table_b tb ON foo'; + + assert.deepEqual(preparedFields, expectedPrepareFields); + + expectedPrepareFields.joins = ''; + stubCall.restore(); + + }); + }); + describe('shouldDestroyConnectionPool', function() { it('should return true', function() { @@ -575,61 +684,75 @@ describe('MySQL', function() { }) }); - describe('Prepare Fields', function() { + describe('Calls', function() { - const expectedPrepareFieldsEmpty = { - where: [], - placeholders: {}, - columns: ['*'], - joins: '' - }; + const validQuery = 'SHOW COLUMNS FROM `table`'; - const expectedPrepareFields = { - where: ['foo = :foo'], - placeholders: { foo: 'bar' }, - columns: ['*'], - joins: '' - }; + const queryError = new Error('Some Query Error'); + queryError.errno = 1206; + queryError.code = 'Invalid Query'; - it.only('should return empty fields with no model or fields', async() => { + const connection = { + config: {}, + query: (q, placeholders = {}) => { + return new Promise((resolve, reject) => { + if(q !== validQuery) + reject(queryError); - const preparedFields = await mysql._prepareFields(); - assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + console.log("Entro Query"); - }); + return resolve([[{ Field: 'foo' }]]); + + }); + }, + release: () => true + }; - it.only('should return empty fields with no fields', async() => { + // const poolStub = sinon.stub(MySQL.prototype, 'pool').get( () => ({ getConnection: () => ({connection}) })); - const preparedFields = await mysql._prepareFields(dummyModel); - assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + it('should thrown MySQLError if query is wrong/invalid ', async() => { + const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') + .returns(connection); - }); + await assert.rejects(mysql._call('SOME INVALID QUERY', {}), { code: MySQLError.codes.INVALID_QUERY }); - it.only('should return the correct where and placeholders fields', async() => { + connectionStub.restore(); + }); - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo' }]]); + it('should return positives results', async() => { + const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') + .returns(connection); - const preparedFields = await mysql._prepareFields(dummyModel, { foo: 'bar' }); + const callQuery = await mysql._call(validQuery, {}); - assert.deepEqual(preparedFields, expectedPrepareFields); + assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); + connectionStub.restore(); + }); - stubCall.restore(); + it('should return MySQLError from Database', async() => { + const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') + .returns() + .throws(new MySQLError('Database Error', MySQLError.codes.CONNECTION_ERROR)); + await assert.rejects(mysql._call(validQuery, {}), { code: MySQLError.codes.CONNECTION_ERROR }); + connectionStub.restore(); }); - it.only('should return empty fields with no fields', async() => { + it('should reconnect', async() => { + const connectionStub = sinon.stub(MySQL.prototype, 'getConnection'); - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo' }]]); + connectionStub + .onCall(0) + .rejects(new MySQLError('Too Many Connections', MySQLError.codes.TOO_MANY_CONNECTION)); - const preparedFields = await mysql._prepareFields(dummyModel, {foo: 'bar_t'}); - - //console.log(preparedFields); - //assert.deepEqual(preparedFields, expectedPrepareFields); + connectionStub + .onCall(1) + .returns(connection); - stubCall.restore(); + const callQuery = await mysql._call(validQuery, {}); + assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); + connectionStub.restore(); }); }); From ebe98583b3f53038c819706f8f6a765927d1cc4b Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 6 Jun 2019 11:37:23 -0300 Subject: [PATCH 08/35] Changed Promises to Async --- mysql/mysql-error.js | 3 +- mysql/mysql.js | 90 ++++++++++++++++---------------------------- 2 files changed, 35 insertions(+), 58 deletions(-) diff --git a/mysql/mysql-error.js b/mysql/mysql-error.js index 2c8a426..c51ec0d 100644 --- a/mysql/mysql-error.js +++ b/mysql/mysql-error.js @@ -10,7 +10,8 @@ class MySQLError extends Error { INVALID_DATA: 3, TOO_MANY_CONNECTION: 4, CONNECTION_ERROR: 5, - INVALID_QUERY: 6 + INVALID_QUERY: 6, + ALL_POOL_ENDED: 7 }; } diff --git a/mysql/mysql.js b/mysql/mysql.js index 73337fc..adf1b76 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -124,25 +124,24 @@ class MySQL { * Get the pool connection. * @returns {Promise} Pool Promise Connection or MySQLError */ - getConnection() { - return new Promise(async(resolve, reject) => { + async getConnection() { - try { + try { - const poolConnection = await this.pool.getConnection(); - poolConnection.connection.config.queryFormat = this._queryFormat.bind(this); - this.constructor.connectionPool = poolConnection.connection; + const poolConnection = await this.pool.getConnection(); + poolConnection.connection.config.queryFormat = this._queryFormat.bind(this); + this.constructor.connectionPool = poolConnection.connection; - resolve(poolConnection); + return poolConnection; - } catch(error) { + } catch(error) { - if(error.code === 'ER_CON_COUNT_ERROR') - reject(new MySQLError(error.code, MySQLError.codes.TOO_MANY_CONNECTION)); + if(error.code === 'ER_CON_COUNT_ERROR') + throw new MySQLError(error.code, MySQLError.codes.TOO_MANY_CONNECTION); + + throw new MySQLError(error.code, MySQLError.codes.CONNECTION_ERROR); + } - reject(new MySQLError(error.code, MySQLError.codes.CONNECTION_ERROR)); - } - }); } /** @@ -227,10 +226,8 @@ class MySQL { return rows; } catch(error) { - // Connections Limit - if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) { - // Retry + if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) { // Retry let retryFunction; setTimeout(retryFunction = () => this._call.apply(this, arguments), 500); return retryFunction(); @@ -238,12 +235,12 @@ class MySQL { // Other Connections Errors if(error.code === MySQLError.codes.CONNECTION_ERROR) { logger.error('Database', error.message); - return error; + throw error; } // Query Errors logger.error('Query', error.errno, error.code, error.message); logger.debug(query, placeholders); - return new MySQLError(error.code, MySQLError.codes.INVALID_QUERY); + throw new MySQLError(error.code, MySQLError.codes.INVALID_QUERY); } } @@ -413,11 +410,8 @@ class MySQL { item = this._mapFields(item); - if(!Object.keys(item).some(field => typeof tableFields[field] !== 'undefined')) { - return Promise.reject( - new MySQLError('Insert must have fields', MySQLError.codes.EMPTY_FIELDS) - ); - } + if(!Object.keys(item).some(field => typeof tableFields[field] !== 'undefined')) + throw new MySQLError('Insert must have fields', MySQLError.codes.EMPTY_FIELDS) if(tableFields.date_created) item.date_created = item.date_created || time; @@ -501,11 +495,8 @@ class MySQL { }); - if(!fields.length) { - return Promise.reject( - new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS) - ); - } + if(!fields.length) + throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS) const clause = where.length ? `WHERE ${where.join(' AND ')}` : ''; @@ -596,11 +587,8 @@ class MySQL { /* istanbul ignore next */ async multiInsert(model, items) { - if(!items || !items.length) { - return Promise.reject( - new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS) - ); - } + if(!items || !items.length) + throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS) const table = model.getTable(); const { dbname } = model; @@ -648,11 +636,8 @@ class MySQL { duplicateUpdate.push(`${key} = VALUES(${key})`); } - if(!itemValues.length) { - return Promise.reject( - new MySQLError('Values cannot be empty', MySQLError.codes.INVALID_STATEMENT) - ); - } + if(!itemValues.length) + throw new MySQLError('Values cannot be empty', MySQLError.codes.INVALID_STATEMENT) values.push(`(${itemValues.join(',')})`); @@ -677,12 +662,8 @@ class MySQL { */ async remove(model, fields) { - if(!Utils.isObject(fields) - || Utils.isEmptyObject(fields)) { - return Promise.reject( - new MySQLError('Invalid fields', MySQLError.codes.INVALID_DATA) - ); - } + if(!Utils.isObject(fields)|| Utils.isEmptyObject(fields)) + throw new MySQLError('Invalid fields', MySQLError.codes.INVALID_DATA); const table = model.getTable(); const { dbname } = model; @@ -710,22 +691,17 @@ class MySQL { */ /* istanbul ignore next */ end() { - return new Promise((resolve, reject) => { - - if(this.closeIddleConnectionsInterval) - clearInterval(this.closeIddleConnectionsInterval); - if(!this._pool) - resolve(); - - this.pool.end(err => { + if(this.closeIddleConnectionsInterval) + clearInterval(this.closeIddleConnectionsInterval); - // all connections in the pool have ended - if(err) - return reject(err); + if(!this._pool) + return; - resolve(); - }); + this.pool.end(err => { + // all connections in the pool have ended + if(err) + throw new MySQLError('All Pool Have Ended', MySQLError.codes.ALL_POOL_ENDED); }); } From 7e6fa77faea64b21b0692f11cfb2aab34147caec Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 6 Jun 2019 17:45:58 -0300 Subject: [PATCH 09/35] Added MySqlError codes --- mysql/mysql-error.js | 11 ++++++----- mysql/mysql.js | 46 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/mysql/mysql-error.js b/mysql/mysql-error.js index c51ec0d..fbee18c 100644 --- a/mysql/mysql-error.js +++ b/mysql/mysql-error.js @@ -7,11 +7,12 @@ class MySQLError extends Error { return { EMPTY_FIELDS: 1, INVALID_STATEMENT: 2, - INVALID_DATA: 3, - TOO_MANY_CONNECTION: 4, - CONNECTION_ERROR: 5, - INVALID_QUERY: 6, - ALL_POOL_ENDED: 7 + INVALID_MODEL: 3, + INVALID_DATA: 4, + INVALID_QUERY: 5, + TOO_MANY_CONNECTION: 6, + CONNECTION_ERROR: 7, + ALL_POOL_ENDED: 8 }; } diff --git a/mysql/mysql.js b/mysql/mysql.js index adf1b76..6cac9cd 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -89,6 +89,7 @@ class MySQL { return this._pool; } + /* istanbul ignore next */ get knex() { if(!this._knex) { @@ -107,7 +108,6 @@ class MySQL { return this._knex; } - /* istanbul ignore next */ _queryFormat(query, values) { if(!values) return query; @@ -135,11 +135,10 @@ class MySQL { return poolConnection; } catch(error) { - if(error.code === 'ER_CON_COUNT_ERROR') throw new MySQLError(error.code, MySQLError.codes.TOO_MANY_CONNECTION); - throw new MySQLError(error.code, MySQLError.codes.CONNECTION_ERROR); + throw new MySQLError(error.code || 'Database Error', MySQLError.codes.CONNECTION_ERROR); } } @@ -222,7 +221,6 @@ class MySQL { const connection = await this.getConnection(); const rows = await connection.query(query, placeholders); connection.release(); - return rows; } catch(error) { @@ -250,6 +248,9 @@ class MySQL { */ async _getFields(model) { + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + const table = model.getTable(); const { dbname } = model; @@ -345,7 +346,11 @@ class MySQL { * @param {string} suffix * @return {object} { where and placeholders } */ - async _prepareFields(model = {}, fields = {}, tableAlias = '', suffix = '') { + async _prepareFields(model, fields = {}, tableAlias = '', suffix = '') { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + let where = []; const placeholders = {}; const { dbname } = model; @@ -396,6 +401,9 @@ class MySQL { */ async insert(model, item, allowUpsert = false) { + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + const table = model.getTable(); const { dbname } = model; @@ -411,7 +419,7 @@ class MySQL { item = this._mapFields(item); if(!Object.keys(item).some(field => typeof tableFields[field] !== 'undefined')) - throw new MySQLError('Insert must have fields', MySQLError.codes.EMPTY_FIELDS) + throw new MySQLError('Insert must have fields', MySQLError.codes.EMPTY_FIELDS); if(tableFields.date_created) item.date_created = item.date_created || time; @@ -454,6 +462,9 @@ class MySQL { */ async update(model, item) { + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + const table = model.getTable(); const { dbname } = model; @@ -526,6 +537,9 @@ class MySQL { */ async get(model, params = {}) { + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + const newParams = { ...params }; // necesario para no alterar params y no afectar a las keys de cache if(!newParams.totals) { @@ -556,6 +570,9 @@ class MySQL { */ async getTotals(model) { + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + if(model.lastQueryEmpty) return { total: 0, pages: 0 }; @@ -587,8 +604,11 @@ class MySQL { /* istanbul ignore next */ async multiInsert(model, items) { + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + if(!items || !items.length) - throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS) + throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS); const table = model.getTable(); const { dbname } = model; @@ -607,7 +627,7 @@ class MySQL { const time = Date.now() / 1000 | 0; - for(let i = 0, len = items.length; i < len; i++) { + for(let i = 0; i < items.length; i++) { const itemValues = []; @@ -616,6 +636,9 @@ class MySQL { if(tableFields.date_created) items[i].date_created = items[i].date_created || time; + if(tableFields.date_modified) + items[i].date_modified = time; + const keys = Object.keys(items[i]).sort(); if(i === 0) @@ -637,7 +660,7 @@ class MySQL { } if(!itemValues.length) - throw new MySQLError('Values cannot be empty', MySQLError.codes.INVALID_STATEMENT) + throw new MySQLError('Values cannot be empty', MySQLError.codes.INVALID_STATEMENT); values.push(`(${itemValues.join(',')})`); @@ -662,7 +685,10 @@ class MySQL { */ async remove(model, fields) { - if(!Utils.isObject(fields)|| Utils.isEmptyObject(fields)) + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!Utils.isObject(fields) || Utils.isEmptyObject(fields)) throw new MySQLError('Invalid fields', MySQLError.codes.INVALID_DATA); const table = model.getTable(); From c319889ee8eeeea42af8cb490ab46330bd015b5f Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 6 Jun 2019 18:17:11 -0300 Subject: [PATCH 10/35] Added more tests --- mysql/mysql.js | 1 + package-lock.json | 33 +++++++++++ package.json | 3 +- tests/mysql-test.js | 136 ++++++++++++++++++++++++++++++-------------- 4 files changed, 130 insertions(+), 43 deletions(-) diff --git a/mysql/mysql.js b/mysql/mysql.js index 6cac9cd..d85ab39 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -81,6 +81,7 @@ class MySQL { * Returns a MySQL pool, if it didn't exist create one. * @returns {object} MySQL pool instance */ + /* istanbul ignore next */ get pool() { if(!this._pool) diff --git a/package-lock.json b/package-lock.json index 1f49873..60361eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2608,6 +2608,24 @@ } } }, + "mock-require": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz", + "integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==", + "dev": true, + "requires": { + "get-caller-file": "^1.0.2", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + } + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -2733,6 +2751,15 @@ "validate-npm-package-license": "^3.0.1" } }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -3305,6 +3332,12 @@ "es6-error": "^4.0.1" } }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", diff --git a/package.json b/package.json index e71a6b3..c6b98ce 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "export TEST_ENV=true; mocha --exit -R nyan --recursive tests/", - "watch-test": "export TEST_ENV=true; mocha --exit -R nyan -w --recursive", + "watch-test": "export TEST_ENV=true; mocha --exit -R nyan -w --recursive tests/", "test-ci": "nyc --reporter=html --reporter=text mocha --recursive tests/", "coverage": "nyc npm test" }, @@ -30,6 +30,7 @@ "eslint-config-airbnb-base": "^13.1.0", "eslint-plugin-import": "^2.14.0", "mocha": "^5.2.0", + "mock-require": "^3.0.3", "nyc": "^14.1.0", "sinon": "^7.3.2" } diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 06bf30c..c7ff54c 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -207,6 +207,16 @@ describe('MySQL', function() { describe('should throw', function() { + it('when attempting to save with no model', async() => { + await assert.rejects(mysql.save(), { code: MySQLError.codes.INVALID_MODEL }); + + }); + + it('when attempting to update with no model', async() => { + await assert.rejects(mysql.update(), { code: MySQLError.codes.INVALID_MODEL }); + + }); + it('when attempting to save and fields not found in table structure', async function() { await assert.rejects(() => mysql.save(dummyModel, { wrongField: 23 }), MySQLError); }); @@ -273,6 +283,13 @@ describe('MySQL', function() { stubTotals.restore(); }); + + it('Should throws Error when try to get with no model', async() => { + await assert.rejects(mysql.get(), { code: MySQLError.codes.INVALID_MODEL }); + + await assert.rejects(mysql.getTotals(), { code: MySQLError.codes.INVALID_MODEL }); + + }); }); describe('getFields()', function() { @@ -292,6 +309,12 @@ describe('MySQL', function() { stubCall.restore(); }); + + it('should return Error with no model', async() => { + + await assert.rejects(mysql._getFields(), { code: MySQLError.codes.INVALID_MODEL }); + + }); }); describe('remove methods', function() { @@ -320,6 +343,11 @@ describe('MySQL', function() { stubCall.restore(); }); + it('should return Error with no model', async() => { + + await assert.rejects(mysql.remove(), { code: MySQLError.codes.INVALID_MODEL }); + + }); it('should throw when no filters as object given', async function() { @@ -551,10 +579,9 @@ describe('MySQL', function() { joins: '' }; - it('should return empty fields with no model or fields', async() => { + it('should return Error with no model', async() => { - const preparedFields = await mysql._prepareFields(); - assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + await assert.rejects(mysql._prepareFields(), { code: MySQLError.codes.INVALID_MODEL }); }); @@ -694,31 +721,17 @@ describe('MySQL', function() { const connection = { config: {}, - query: (q, placeholders = {}) => { - return new Promise((resolve, reject) => { - if(q !== validQuery) - reject(queryError); - - console.log("Entro Query"); + query: async queryToResolve => { + if(queryToResolve !== validQuery) + throw queryError; - return resolve([[{ Field: 'foo' }]]); - - }); + return ([[{ Field: 'foo' }]]); }, release: () => true }; // const poolStub = sinon.stub(MySQL.prototype, 'pool').get( () => ({ getConnection: () => ({connection}) })); - it('should thrown MySQLError if query is wrong/invalid ', async() => { - const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') - .returns(connection); - - await assert.rejects(mysql._call('SOME INVALID QUERY', {}), { code: MySQLError.codes.INVALID_QUERY }); - - connectionStub.restore(); - }); - it('should return positives results', async() => { const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') .returns(connection); @@ -729,15 +742,6 @@ describe('MySQL', function() { connectionStub.restore(); }); - it('should return MySQLError from Database', async() => { - const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') - .returns() - .throws(new MySQLError('Database Error', MySQLError.codes.CONNECTION_ERROR)); - - await assert.rejects(mysql._call(validQuery, {}), { code: MySQLError.codes.CONNECTION_ERROR }); - connectionStub.restore(); - }); - it('should reconnect', async() => { const connectionStub = sinon.stub(MySQL.prototype, 'getConnection'); @@ -754,37 +758,85 @@ describe('MySQL', function() { assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); connectionStub.restore(); }); + + it('should thrown MySQLError if query is wrong/invalid ', async() => { + const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') + .returns(connection); + + await assert.rejects(mysql._call('SOME INVALID QUERY', {}), { code: MySQLError.codes.INVALID_QUERY }); + + connectionStub.restore(); + }); + + + it('should return MySQLError from Database', async() => { + const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') + .rejects(new MySQLError('Database Error', MySQLError.codes.CONNECTION_ERROR)); + + await assert.rejects(mysql._call(validQuery, {}), { code: MySQLError.codes.CONNECTION_ERROR }); + connectionStub.restore(); + }); + }); - /* describe('GetConnection', () => { + describe('GetConnection', () => { - const fakeConnection = { + const connection = { config: {} }; - const fakePoolWithError = { - getConnection: cb => cb(new Error('some database error'), null) - }; - const fakePoolValid = { - getConnection: cb => cb(null, fakeConnection) + getConnection: () => ({ connection }) }; it('should get Error when connection is not working', async() => { - const stubPool = sinon.stub(MySQL.prototype, 'pool').get(() => fakePoolWithError); - await assert.rejects(mysql.getConnection(), { message: 'some database error' }); + const stubPool = sinon.stub(MySQL.prototype, 'pool') + .get(() => { throw new Error('Database Error'); }); + + await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.CONNECTION_ERROR }); + stubPool.restore(); + }); + + it('should get MySqlError when Pool have too many connection', async() => { + + const SqlErrorTooManyConnection = new Error('Too Many Connection'); + SqlErrorTooManyConnection.code = 'ER_CON_COUNT_ERROR'; + + const stubPool = sinon.stub(MySQL.prototype, 'pool') + .get(() => { throw SqlErrorTooManyConnection; }); + + await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.TOO_MANY_CONNECTION }); + stubPool.restore(); }); it('should connect and add QueryFormat', async() => { const stubPool = sinon.stub(MySQL.prototype, 'pool').get(() => fakePoolValid); - fakeConnection.config.queryFormat = MySQL.queryFormat; + connection.config.queryFormat = MySQL.queryFormat; await assert(typeof mysql.getConnection(), 'object'); - await assert.deepEqual(await mysql.getConnection(), fakeConnection); + assert.deepEqual(await mysql.getConnection(), { connection }); + stubPool.restore(); }); - }); */ + }); + + describe('Query Format', function() { + + const queryString = value => `SHOW COLUMNS FROM ${value}`; + + it('should return the same Query String if value dont exist', () => { + assert.equal(mysql._queryFormat(queryString('`table`')), queryString('`table`')); + }); + + it('should return the Query String with correct value change', () => { + assert.equal(mysql._queryFormat(queryString(':foo'), { foo: 'bar' }), queryString('\'bar\'')); + }); + + it('should return the Query String with correct value format', () => { + assert.equal(mysql._queryFormat(queryString(':foo'), 'foo'), queryString(':foo')); + }); + }); }); From 837579b4c4c461754843f9245130df348fa8cfeb Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 7 Jun 2019 11:31:34 -0300 Subject: [PATCH 11/35] Coverage improved --- mysql/mysql.js | 7 +++++-- tests/mysql-test.js | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mysql/mysql.js b/mysql/mysql.js index d85ab39..e5221e2 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -81,7 +81,6 @@ class MySQL { * Returns a MySQL pool, if it didn't exist create one. * @returns {object} MySQL pool instance */ - /* istanbul ignore next */ get pool() { if(!this._pool) @@ -110,6 +109,10 @@ class MySQL { } _queryFormat(query, values) { + + console.log('**************') + console.log(escape); + if(!values) return query; @@ -701,7 +704,7 @@ class MySQL { joins } = await this._prepareFields(model, fields); - const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : ''; + const whereClause = `WHERE ${where.join(' AND ')}` // where.length ? `WHERE ${where.join(' AND ')}` : ''; const joinsClause = joins || ''; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index c7ff54c..2876392 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -77,6 +77,10 @@ describe('MySQL', function() { assert.equal('_columns', MySQL.columns); }); + it('should return "_columns"', () => { + assert(mysql.pool); + }); + }); describe('connecionPool - setter and getter', function() { From 247e4e4057d652dd49c3773139920c69c69ef679 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 7 Jun 2019 14:34:41 -0300 Subject: [PATCH 12/35] Coverage improved --- mysql/mysql.js | 3 --- tests/mysql-test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/mysql/mysql.js b/mysql/mysql.js index e5221e2..1c730fc 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -110,9 +110,6 @@ class MySQL { _queryFormat(query, values) { - console.log('**************') - console.log(escape); - if(!values) return query; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 2876392..752d9cc 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -259,6 +259,35 @@ describe('MySQL', function() { stub.restore(); }); + it('Should return results and totals only with filters', async function() { + + const originalParams = { someFilter: 'foo' }; + const params = { ...originalParams }; + + const stubResults = stubExecute([{ result: 1 }, { result: 2 }]); + + const result = await mysql.get(dummyModel, params); + + assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); + + testParams(params, originalParams); + + stubResults.restore(); + + const stubTotals = stubExecute([{ count: 650 }]); + + const resultTotals = await mysql.getTotals(dummyModel); + + assert.deepEqual(resultTotals, { + total: 650, + page: 1, + pageSize: 500, + pages: 2 + }); + + stubTotals.restore(); + }); + it('Should return results and totals', async function() { const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; From 7592acf9434696bcd3380e1a8bacce15998f3e39 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 7 Jun 2019 18:29:52 -0300 Subject: [PATCH 13/35] Coverage improved --- mysql/mysql.js | 14 +++++-------- tests/mysql-test.js | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/mysql/mysql.js b/mysql/mysql.js index 1c730fc..1ba08ea 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -295,14 +295,13 @@ class MySQL { const where = []; const placeholders = {}; - const fieldKey = `${alias ? alias + '.' : ''}${field}`; - if(value instanceof Array) { + if(Array.isArray(value)) { + const queryIn = []; const hasNull = value.some(item => item === null); - if(hasNull) value = value.filter(item => item !== null); @@ -315,12 +314,9 @@ class MySQL { } - if(queryIn.length) { - - if(hasNull) - where.push(`(${fieldKey} IS NULL OR ${fieldKey} IN (${queryIn.join(',')}))`); - else where.push(`${fieldKey} IN (${queryIn.join(',')})`); - } + if(hasNull) + where.push(`(${fieldKey} IS NULL OR ${fieldKey} IN (${queryIn.join(',')}))`); + else where.push(`${fieldKey} IN (${queryIn.join(',')})`); } else { diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 752d9cc..92e65be 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -207,6 +207,24 @@ describe('MySQL', function() { assert.deepEqual(stubCall.args[0][1], { set_foo: 'bar', foo: 'barr' }); }); + it('if getFields not return id, date_created and date_modified', async() => { + + const fields2 = { some: 'some' }; + stubFields.returns(fields2); + + const result = await mysql.insert(dummyModel, { + some: 'bar' + }, false); + + const expectedQuery = sanitizeQuery(`INSERT INTO ${fullTableName} + (some) VALUES (:some)`); + + assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); + + assert.equal(result, insertId); + + }); + }); describe('should throw', function() { @@ -240,6 +258,22 @@ describe('MySQL', function() { assert.deepEqual(params, expectedParams, 'shouldn\'t modify ofiginal params'); }; + it('Get Totals in empty Table, should return default values', async() => { + + const stub2 = sinon.stub(MySQL.prototype, 'get').callsFake(() => [{ count: 0 }]); + + const totalExpected = { + page: 1, + pageSize: 500, + pages: 1, + total: 0 + }; + + assert.deepEqual(await mysql.getTotals(dummyModel), totalExpected); + + stub2.restore(); + }); + it('Should return empty results and totals with zero values', async function() { const params = {}; @@ -436,6 +470,20 @@ describe('MySQL', function() { }); + it('Should build field query correctly for a single value field with alias', function() { + + const values = [['foo', 'test'], ['bar', 1]]; + const alias = 'test'; + + for(const [field, value] of values) { + const rows = MySQL._buildFieldQuery(field, value, alias); + + assert.deepEqual(rows.where, [`${alias}.${field} = :${field}`]); + assert.deepEqual(rows.placeholders, { [field]: value }); + } + + }); + it('Should build field query correctly for a single value field with added suffix to placeholder', function() { const values = [['foo', 'test'], ['bar', 1]]; From c55b3ce55e38a5286f6b562cf0767d304019a1cc Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Mon, 10 Jun 2019 18:21:46 -0300 Subject: [PATCH 14/35] Added Mock --- tests/mysql-mock.js | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/mysql-mock.js diff --git a/tests/mysql-mock.js b/tests/mysql-mock.js new file mode 100644 index 0000000..eaeb97b --- /dev/null +++ b/tests/mysql-mock.js @@ -0,0 +1,60 @@ +'use strict'; + +const MySQL = require('mysql2/promise'); + +const queryError = new Error('Invalid Error'); +queryError.errno = 1206; +queryError.code = 'Invalid Query'; + +MySQL.addValidQuery = function(query, resultExpected) { + if(!this._queries) + this._queries = {}; + this._queries[query] = resultExpected; +}; + +MySQL.isValidQuery = function(query) { + return !!this._queries[query]; +}; + +MySQL.getValidQueryResult = function(query) { + return this._queries[query]; +}; + +MySQL.createPool = () => { + return ({ + getConnection: async() => ({ + connection: { + addValidQuery(query, resultExpected) { + if(!this._queries) + this._queries = {}; + const count = Object.keys(this._queries).length + 1; + this._queries[count] = { + query, + results: resultExpected + }; + }, + isValidQuery(queryToFind) { + if(!this._queries) + return false; + const query = Object.values(this._queries).filter(element => element.query === queryToFind); + return !!query; + + }, + getValidQueryResult(queryToFind) { + return Object.values(this._queries).filter(element => element.query === queryToFind)[0].results; + }, + config: {}, + async query(queryToResolve, placeholders) { + const queryFormated = this.config.queryFormat(queryToResolve, placeholders); + if(!this.isValidQuery(queryFormated)) + throw queryError; + + return this.getValidQueryResult(queryFormated); + }, + release: () => true + } + }) + }); +}; + +module.exports = MySQL; From a63a8b214e33dc0365944772ee1071044c306bd5 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Mon, 10 Jun 2019 18:33:11 -0300 Subject: [PATCH 15/35] Refactor Tests --- dev.js | 69 +++++++++++++ tests/mysql-test.js | 238 +++++++++++++++++++++++++++++--------------- 2 files changed, 224 insertions(+), 83 deletions(-) create mode 100644 dev.js diff --git a/dev.js b/dev.js new file mode 100644 index 0000000..68a158d --- /dev/null +++ b/dev.js @@ -0,0 +1,69 @@ + +/* eslint-disable */ + +const MYSQL2 = require('./mysql/mysql'); + +const TABLE = "CREATE TABLE IF NOT EXISTS `fake_table6` ( `id` MEDIUMINT UNSIGNED AUTO_INCREMENT NOT NULL UNIQUE, `date_created` INT(11) NOT NULL,`date_modified` INT(11) NOT NULL, `campo_1` VARCHAR(50) NOT NULL, `campo_2` VARCHAR(50) NOT NULL, PRIMARY KEY(id));"; +const INSERT = "INSERT INTO `prueba` (`title`) VALUES (:title);"; +const SHOW_COLUMN = 'SHOW COLUMNS FROM `fizzmod`.`fakeTable`;' + +const config = { + host: "localhost", + user: "root", + password: "123", + database: "fizzmod", + port: 3306 +} + +class fake_model { + + static get name() { + return 'fakeModel'; + } + + static get dbname() { + return 'fizzmod'; + } + + static get table() { + return 'fakeTable6'; + } + + get dbTable() { + return 'fake_table6' + } + + static getTable(){ // Save + return 'fake_table6'; + } + + static get fields() { + return { + campo_1 : true, + campo_2 : true + } + } + + static addDbName(table) { + return 'fakeTable6'; + } +} + +const mysql = new MYSQL2(config); + +// mysql._call(TABLE).then(a => console.log(a)).catch(e => console.log(e.message)); +// mysql._getFields(fake_model).then(a => console.log(a)).catch(e => console.log(e.message)); +// mysql.insert(fake_model, {campo_1 : "Hola15", campo_2 : "pepe5"}).then(a => console.log(a)).catch(e => console.log(e.message)); +// mysql.get(fake_model).then(a => console.log(a)).catch(e => console.log(e.message)); +// mysql.update(fake_model, {campo_1: "Nada"} ).then(a => console.log(a)).catch(e => console.log(e.message)); +// mysql.getTotals(fake_model).then(a => console.log(a)).catch(e => console.log(e.message)); +// mysql.remove(fake_model,{ campo_2: "pepe4"}).then(a => console.log(a)).catch(e => console.log(e.message)); + +// Array(n).fill({}).map((elem,i) => i); + +const o = { + a: "a", + b: "b" +} + +console.log(o.length); \ No newline at end of file diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 92e65be..e6b34be 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -2,21 +2,169 @@ const assert = require('assert'); const sinon = require('sinon'); - +const mock = require('mock-require'); const QueryBuilder = require('@janiscommerce/query-builder'); +const mysql2Mock = require('./mysql-mock'); + +mock('mysql2/promise', mysql2Mock); + const { MySQLError } = require('./../mysql'); const MySQL = require('./../index'); /* eslint-disable prefer-arrow-callback */ +const sandbox = sinon.createSandbox(); + const sanitizeQuery = string => string.replace(/[\n\t]/g, ' ') .replace(/\s+/g, ' ') .trim(); -describe('MySQL', function() { +describe('MySQL module', function() { + + let dummyModel; + let mysql; + + class Model { + getTable() { + return 'table'; + } + } + + + before(() => { + + // stubKnex = sinon.stub(MySQL.prototype, 'knex').get(() => knexGetter); + + /* stubBuild = sinon.stub(QueryBuilder.prototype, 'build').callsFake(() => { + return true; + }); */ + + mysql = new MySQL({}); + dummyModel = new Model(); + dummyModel.dbname = 'dbname'; + + // fullTableName = `${dummyModel.dbname}.${dummyModel.getTable()}`; + }); + + after(() => { + sandbox.restore(); + mock.stopAll(); + }); + + describe('Connections Test', function() { + + describe('ConnecionPool - setter and getter', function() { + + it('Should return empty object when no pool connection was set', () => { + + assert(typeof MySQL.connectionPool, 'Object'); + assert.deepEqual({}, MySQL.connectionPool, 'it should by {}'); + }); + + it('Should set a connection', function() { + + const threadId = 123; + MySQL.connectionPool = { threadId }; + + assert(typeof MySQL.connectionPool, 'Object'); + assert(typeof MySQL.connectionPool[threadId], 'Object'); + assert(typeof MySQL.connectionPool[threadId].lastActivity, 'number'); + + const now = Date.now() / 1000 | 0; + + assert(MySQL.connectionPool[threadId].lastActivity >= now); + assert.equal(MySQL.connectionPool[threadId].id, threadId); + + }); + + }); + + describe('Pool Getter', function() { + + it('\'_pool\' field must be \'undefined\' before get \'pool\'', function() { + + assert.equal(typeof mysql._pool, 'undefined'); + assert.equal(typeof mysql.pool, 'object'); + + }); + + it('After get \'pool\' must be Pool must be setted and be equal', function() { + + assert.equal(typeof mysql.pool, 'object'); + assert.deepEqual(mysql.pool, mysql._pool); + + }); + + }); + + describe('Getting Connection', function() { + + it('should connect and add QueryFormat', async function() { + + const poolConnection = await mysql.getConnection(); + + assert(typeof poolConnection, 'object'); + assert(poolConnection.connection.config.queryFormat); + }); + + it('should get Error when connection is not working', async function() { + + sandbox.stub(MySQL.prototype, 'pool') + .get(() => { throw new Error('Database Error'); }); + + await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.CONNECTION_ERROR }); + }); + + it('should get MySqlError when Pool have too many connection', async function() { + + const SqlErrorTooManyConnection = new Error('Too Many Connection'); + SqlErrorTooManyConnection.code = 'ER_CON_COUNT_ERROR'; - let stubKnex; + sandbox.stub(MySQL.prototype, 'pool') + .get(() => { throw SqlErrorTooManyConnection; }); + + await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.TOO_MANY_CONNECTION }); + + }); + + }); + + }); + + describe('Field Preparations', function () { + describe('getFields()', function() { + + it('Should return formatted fields', async function() { + + const stubCall = sinon.stub(MySQL.prototype, '_call') + .returns([[{ Field: 'foo', extra: 1 }]]); + + const fields = await mysql._getFields(dummyModel); + + assert.deepEqual(fields, { + foo: { Field: 'foo', extra: 1 } + }); + + assert.equal(stubCall.args[0][0], `SHOW COLUMNS FROM ${fullTableName}`); + + stubCall.restore(); + }); + + it('should return Error with no model', async() => { + + await assert.rejects(mysql._getFields(), { code: MySQLError.codes.INVALID_MODEL }); + + }); + }); + }) + +}); + + +/* Old Tests + +let stubKnex; let stubBuild; let dummyModel; @@ -52,7 +200,7 @@ describe('MySQL', function() { stubBuild.restore(); }); - describe('static getters', () => { +describe('static getters', () => { it('should return default limit', () => { const DEFAULT_LIMIT = 500; @@ -77,42 +225,9 @@ describe('MySQL', function() { assert.equal('_columns', MySQL.columns); }); - it('should return "_columns"', () => { - assert(mysql.pool); - }); - }); - describe('connecionPool - setter and getter', function() { - - it('should return empty object when no pool connection was set', () => { - - assert(typeof MySQL.connectionPool, 'Object'); - - assert.deepEqual({}, MySQL.connectionPool, 'it should by {}'); - }); - - it('should set a connection', function() { - - const threadId = 123; - - MySQL.connectionPool = { threadId }; - - assert(typeof MySQL.connectionPool, 'Object'); - - assert(typeof MySQL.connectionPool[threadId], 'Object'); - - assert(typeof MySQL.connectionPool[threadId].lastActivity, 'number'); - - const now = Date.now() / 1000 | 0; - - assert(MySQL.connectionPool[threadId].lastActivity >= now); - - assert.equal(MySQL.connectionPool[threadId].id, threadId); - - }); - - }); + describe('save methods', function() { @@ -860,50 +975,6 @@ describe('MySQL', function() { }); - describe('GetConnection', () => { - - const connection = { - config: {} - }; - - const fakePoolValid = { - getConnection: () => ({ connection }) - }; - - it('should get Error when connection is not working', async() => { - - const stubPool = sinon.stub(MySQL.prototype, 'pool') - .get(() => { throw new Error('Database Error'); }); - - await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.CONNECTION_ERROR }); - stubPool.restore(); - }); - - it('should get MySqlError when Pool have too many connection', async() => { - - const SqlErrorTooManyConnection = new Error('Too Many Connection'); - SqlErrorTooManyConnection.code = 'ER_CON_COUNT_ERROR'; - - const stubPool = sinon.stub(MySQL.prototype, 'pool') - .get(() => { throw SqlErrorTooManyConnection; }); - - await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.TOO_MANY_CONNECTION }); - - stubPool.restore(); - }); - - it('should connect and add QueryFormat', async() => { - const stubPool = sinon.stub(MySQL.prototype, 'pool').get(() => fakePoolValid); - connection.config.queryFormat = MySQL.queryFormat; - - await assert(typeof mysql.getConnection(), 'object'); - assert.deepEqual(await mysql.getConnection(), { connection }); - - stubPool.restore(); - }); - - }); - describe('Query Format', function() { const queryString = value => `SHOW COLUMNS FROM ${value}`; @@ -920,4 +991,5 @@ describe('MySQL', function() { assert.equal(mysql._queryFormat(queryString(':foo'), 'foo'), queryString(':foo')); }); }); -}); + +*/ \ No newline at end of file From 5d1be50a209203000670b03e10268debc2a1b8b8 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Tue, 11 Jun 2019 16:56:23 -0300 Subject: [PATCH 16/35] Added Mock --- mocks/mysql-mock.js | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 mocks/mysql-mock.js diff --git a/mocks/mysql-mock.js b/mocks/mysql-mock.js new file mode 100644 index 0000000..f85fd96 --- /dev/null +++ b/mocks/mysql-mock.js @@ -0,0 +1,57 @@ +'use strict'; + +const MySQL = require('mysql2/promise'); + +const queryError = new Error('Invalid Error'); +queryError.errno = 1206; +queryError.code = 'Invalid Query'; + +const validQueryManager = { + addValidQuery(query, resultExpected) { + if(!this._queries) + this._queries = {}; + const count = Object.keys(this._queries).length + 1; + this._queries[count] = { + query, + results: resultExpected + }; + }, + isValidQuery(queryToFind) { + if(!this._queries) + return false; + + const query = Object.values(this._queries).filter(element => element.query === queryToFind); + return !!query.length; + + }, + getValidQueryResult(queryToFind) { + return Object.values(this._queries).filter(element => element.query === queryToFind)[0].results; + }, + cleanQueries() { + if(this._queries) + this._queries = null; + } +}; + +MySQL.createPool = () => { + return ({ + getConnection: async() => ({ + connection: { + config: {} + }, + async query(queryToResolve, placeholders) { + const queryFormated = this.connection.config.queryFormat(queryToResolve, placeholders); + if(!validQueryManager.isValidQuery(queryFormated)) + throw queryError; + + return validQueryManager.getValidQueryResult(queryFormated); + }, + release: () => true + }) + }); +}; + +module.exports = { + MySQL, + validQueryManager +}; From 79cc5ed8612b6cf4cc5bc8bf4a36f20198d09aaa Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Tue, 11 Jun 2019 16:56:59 -0300 Subject: [PATCH 17/35] Added units tests, and improved coverage --- .nycrc | 3 +- dev.js | 69 ---- mysql/mysql.js | 7 +- tests/mysql-mock.js | 60 --- tests/mysql-test.js | 985 +++++++++++++++++++------------------------- 5 files changed, 440 insertions(+), 684 deletions(-) delete mode 100644 dev.js delete mode 100644 tests/mysql-mock.js diff --git a/.nycrc b/.nycrc index a78e273..71004bc 100644 --- a/.nycrc +++ b/.nycrc @@ -1,7 +1,8 @@ { "exclude": [ "tests/", - ".eslintrc.js" + ".eslintrc.js", + "mocks/" ], "extension": [ ".js" diff --git a/dev.js b/dev.js deleted file mode 100644 index 68a158d..0000000 --- a/dev.js +++ /dev/null @@ -1,69 +0,0 @@ - -/* eslint-disable */ - -const MYSQL2 = require('./mysql/mysql'); - -const TABLE = "CREATE TABLE IF NOT EXISTS `fake_table6` ( `id` MEDIUMINT UNSIGNED AUTO_INCREMENT NOT NULL UNIQUE, `date_created` INT(11) NOT NULL,`date_modified` INT(11) NOT NULL, `campo_1` VARCHAR(50) NOT NULL, `campo_2` VARCHAR(50) NOT NULL, PRIMARY KEY(id));"; -const INSERT = "INSERT INTO `prueba` (`title`) VALUES (:title);"; -const SHOW_COLUMN = 'SHOW COLUMNS FROM `fizzmod`.`fakeTable`;' - -const config = { - host: "localhost", - user: "root", - password: "123", - database: "fizzmod", - port: 3306 -} - -class fake_model { - - static get name() { - return 'fakeModel'; - } - - static get dbname() { - return 'fizzmod'; - } - - static get table() { - return 'fakeTable6'; - } - - get dbTable() { - return 'fake_table6' - } - - static getTable(){ // Save - return 'fake_table6'; - } - - static get fields() { - return { - campo_1 : true, - campo_2 : true - } - } - - static addDbName(table) { - return 'fakeTable6'; - } -} - -const mysql = new MYSQL2(config); - -// mysql._call(TABLE).then(a => console.log(a)).catch(e => console.log(e.message)); -// mysql._getFields(fake_model).then(a => console.log(a)).catch(e => console.log(e.message)); -// mysql.insert(fake_model, {campo_1 : "Hola15", campo_2 : "pepe5"}).then(a => console.log(a)).catch(e => console.log(e.message)); -// mysql.get(fake_model).then(a => console.log(a)).catch(e => console.log(e.message)); -// mysql.update(fake_model, {campo_1: "Nada"} ).then(a => console.log(a)).catch(e => console.log(e.message)); -// mysql.getTotals(fake_model).then(a => console.log(a)).catch(e => console.log(e.message)); -// mysql.remove(fake_model,{ campo_2: "pepe4"}).then(a => console.log(a)).catch(e => console.log(e.message)); - -// Array(n).fill({}).map((elem,i) => i); - -const o = { - a: "a", - b: "b" -} - -console.log(o.length); \ No newline at end of file diff --git a/mysql/mysql.js b/mysql/mysql.js index 1ba08ea..8c11e86 100644 --- a/mysql/mysql.js +++ b/mysql/mysql.js @@ -171,7 +171,6 @@ class MySQL { parsed = field.replace(/[A-Z]/g, (match, index) => (index !== 0 ? '_' : '') + match.toLowerCase()); } - return parsed; } @@ -218,7 +217,6 @@ class MySQL { async _call(query, placeholders = {}) { try { - const connection = await this.getConnection(); const rows = await connection.query(query, placeholders); connection.release(); @@ -227,10 +225,11 @@ class MySQL { } catch(error) { // Connections Limit if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) { // Retry - let retryFunction; - setTimeout(retryFunction = () => this._call.apply(this, arguments), 500); + const retryFunction = () => this._call.apply(this, arguments); + setTimeout(retryFunction, 1000); return retryFunction(); } + // Other Connections Errors if(error.code === MySQLError.codes.CONNECTION_ERROR) { logger.error('Database', error.message); diff --git a/tests/mysql-mock.js b/tests/mysql-mock.js deleted file mode 100644 index eaeb97b..0000000 --- a/tests/mysql-mock.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -const MySQL = require('mysql2/promise'); - -const queryError = new Error('Invalid Error'); -queryError.errno = 1206; -queryError.code = 'Invalid Query'; - -MySQL.addValidQuery = function(query, resultExpected) { - if(!this._queries) - this._queries = {}; - this._queries[query] = resultExpected; -}; - -MySQL.isValidQuery = function(query) { - return !!this._queries[query]; -}; - -MySQL.getValidQueryResult = function(query) { - return this._queries[query]; -}; - -MySQL.createPool = () => { - return ({ - getConnection: async() => ({ - connection: { - addValidQuery(query, resultExpected) { - if(!this._queries) - this._queries = {}; - const count = Object.keys(this._queries).length + 1; - this._queries[count] = { - query, - results: resultExpected - }; - }, - isValidQuery(queryToFind) { - if(!this._queries) - return false; - const query = Object.values(this._queries).filter(element => element.query === queryToFind); - return !!query; - - }, - getValidQueryResult(queryToFind) { - return Object.values(this._queries).filter(element => element.query === queryToFind)[0].results; - }, - config: {}, - async query(queryToResolve, placeholders) { - const queryFormated = this.config.queryFormat(queryToResolve, placeholders); - if(!this.isValidQuery(queryFormated)) - throw queryError; - - return this.getValidQueryResult(queryFormated); - }, - release: () => true - } - }) - }); -}; - -module.exports = MySQL; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index e6b34be..019563a 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -5,7 +5,7 @@ const sinon = require('sinon'); const mock = require('mock-require'); const QueryBuilder = require('@janiscommerce/query-builder'); -const mysql2Mock = require('./mysql-mock'); +const { MySQL: mysql2Mock, validQueryManager } = require('../mocks/mysql-mock'); mock('mysql2/promise', mysql2Mock); @@ -22,36 +22,30 @@ const sanitizeQuery = string => string.replace(/[\n\t]/g, ' ') describe('MySQL module', function() { - let dummyModel; - let mysql; - class Model { getTable() { return 'table'; } } + const mysql = new MySQL({}); - before(() => { - - // stubKnex = sinon.stub(MySQL.prototype, 'knex').get(() => knexGetter); - - /* stubBuild = sinon.stub(QueryBuilder.prototype, 'build').callsFake(() => { - return true; - }); */ - - mysql = new MySQL({}); - dummyModel = new Model(); - dummyModel.dbname = 'dbname'; + const dummyModel = new Model(); + dummyModel.dbname = 'dbname'; - // fullTableName = `${dummyModel.dbname}.${dummyModel.getTable()}`; - }); + const fullTableName = `${dummyModel.dbname}.${dummyModel.getTable()}`; after(() => { sandbox.restore(); mock.stopAll(); }); + + afterEach(() => { + sandbox.restore(); + validQueryManager.cleanQueries(); + }); + describe('Connections Test', function() { describe('ConnecionPool - setter and getter', function() { @@ -101,7 +95,6 @@ describe('MySQL module', function() { describe('Getting Connection', function() { it('should connect and add QueryFormat', async function() { - const poolConnection = await mysql.getConnection(); assert(typeof poolConnection, 'object'); @@ -130,106 +123,436 @@ describe('MySQL module', function() { }); + describe('Making Calls', function() { + + const validQuery = `SHOW COLUMNS FROM ${fullTableName}`; + + const queryError = new Error('Some Query Error'); + queryError.errno = 1206; + queryError.code = 'Invalid Query'; + + const connection = { + config: {}, + query: async queryToResolve => { + if(queryToResolve !== validQuery) + throw queryError; + + return ([[{ Field: 'foo' }]]); + }, + release: () => true + }; + + it('should return positives results', async() => { + validQueryManager.addValidQuery(validQuery, [[{ Field: 'foo' }]]); + + const callQuery = await mysql._call(validQuery, {}); + + assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); + }); + + it('should thrown MySQLError if query is wrong/invalid ', async() => { + + await assert.rejects(mysql._call('SOME INVALID QUERY', {}), { code: MySQLError.codes.INVALID_QUERY }); + }); + + it('should return MySQLError from Database', async() => { + sandbox.stub(MySQL.prototype, 'getConnection') + .rejects(new MySQLError('Database Error', MySQLError.codes.CONNECTION_ERROR)); + + await assert.rejects(mysql._call(validQuery, {}), { code: MySQLError.codes.CONNECTION_ERROR }); + }); + + it('should reconnect', async() => { + + const connectionStub = sandbox.stub(MySQL.prototype, 'getConnection'); + + connectionStub + .onCall(0) + .rejects(new MySQLError('Too Many Connections', MySQLError.codes.TOO_MANY_CONNECTION)); + + connectionStub + .onCall(1) + .returns(connection); + + const callQuery = await mysql._call(validQuery, {}); + + assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); + }); + + }); + + describe('Should Destroy Connection Pool', function() { + + it('should return true', function() { + + const now = Date.now() / 1000 | 0; + + const dates = [ + now - MySQL.maxIddleTimeout - 50 // 50 seconds after iddle timeout limit + ]; + + dates.forEach(lastActivity => { + assert(mysql._shouldDestroyConnectionPool(lastActivity)); + }); + + assert(mysql._shouldDestroyConnectionPool()); + }); + + it('should return false', function() { + + const now = Date.now() / 1000 | 0; + + const dates = [ + now - MySQL.maxIddleTimeout + 50 // 50 seconds before iddle timeout limit + ]; + + dates.forEach(lastActivity => { + assert(!mysql._shouldDestroyConnectionPool(lastActivity)); + }); + + }); + + }); + }); - describe('Field Preparations', function () { - describe('getFields()', function() { + describe('Field Preparations', function() { + + describe('Query Format', function() { + + const queryString = value => `SHOW COLUMNS FROM ${value}`; + + it('should return the same Query String if value dont exist', function() { + assert.equal(mysql._queryFormat(queryString('`table`')), queryString('`table`')); + }); + + it('should return the Query String with correct value change', function() { + assert.equal(mysql._queryFormat(queryString(':foo'), { foo: 'bar' }), queryString('\'bar\'')); + }); + + it('should return the Query String with correct value format', function() { + assert.equal(mysql._queryFormat(queryString(':foo'), 'foo'), queryString(':foo')); + }); + }); + + describe('Field Mapping', function() { + + const fieldsMap = { + order_form_id: ['orderFormId'], + check_boolean: ['checkBoolean'], + check_falsy: ['checkFalsy'], + check_multiple: ['checkMulti', 'checkMultiple'], + check_string: 'checkString', + some_field: ['other_field'] + }; + + it('Should not map anything and get empty object', function() { + assert.deepEqual(mysql._mapItem(), {}); + }); + + it('Should map correctly an object', function() { + + const data = { + orderFormId: 'yes', + checkBoolean: true, + checkFalsy: false, + status: false, + checkMultiple: true, + CapitalField: false, + no: 5, + noMap: 6, + checkString: 'string' + }; + + + const result = mysql._mapFields(data, fieldsMap); + + assert.equal(result.orderFormId, undefined); + assert.equal(result.checkBoolean, undefined); + assert.equal(result.checkFalsy, undefined); + assert.equal(result.checkMultiple, undefined); + assert.equal(result.checkString, undefined); + assert.equal(result.CapitalField, undefined); + + assert.equal(result.order_form_id, 'yes'); + assert.equal(result.check_boolean, true); + assert.equal(result.check_falsy, false); + assert.equal(result.check_multiple, true); + assert.equal(result.status, false); + assert.equal(result.capital_field, false); + assert.equal(result.check_string, 'string'); + assert.equal(result.no_map, 6); + assert.equal(result.no, 5); + + }); + + it('Should map correctly an array of objects ', function() { + + const data = [{ + orderFormId: 'yes', + checkBoolean: true, + checkFalsy: false, + status: false, + checkMultiple: true, + nomap: 5, + checkString: 'string' + }, { + orderFormId: 'yes', + checkBoolean: true, + checkFalsy: false, + status: false, + checkMultiple: true, + nomap: 5, + checkString: 'string' + }]; + + const results = mysql._mapFields(data, fieldsMap); + + results.forEach(result => { + assert.equal(result.orderFormId, undefined); + assert.equal(result.checkBoolean, undefined); + assert.equal(result.checkFalsy, undefined); + assert.equal(result.checkMultiple, undefined); + assert.equal(result.checkString, undefined); + + assert.equal(result.order_form_id, 'yes'); + assert.equal(result.check_boolean, true); + assert.equal(result.check_falsy, false); + assert.equal(result.check_multiple, true); + assert.equal(result.status, false); + assert.equal(result.check_string, 'string'); + assert.equal(result.nomap, 5); + }); + + }); + + it('Should leave symbols properties unchanged', function() { + + const data = [{ + [Symbol.for('mock')]: 'symbol', + nomap: 5 + }, { + [Symbol.for('mock')]: 'symbol', + nomap: 5 + }]; + + const results = mysql._mapFields(data, fieldsMap); + + results.forEach(result => { + assert.equal(result[Symbol.for('mock')], 'symbol'); + assert.equal(result.nomap, 5); + }); + + }); + }); + + describe('Prepare Fields', function() { + + const expectedPrepareFieldsEmpty = { + where: [], + placeholders: {}, + columns: ['*'], + joins: '' + }; + + const expectedPrepareFields = { + where: ['foo = :foo'], + placeholders: { foo: 'bar' }, + columns: ['*'], + joins: '' + }; + + beforeEach(() => { + validQueryManager.addValidQuery(`SHOW COLUMNS FROM ${fullTableName}`, [[{ Field: 'foo' }]]); + }); + + after(() => { + validQueryManager.cleanQueries(); + }); + + it('should return Error with no model', async function() { + + await assert.rejects(mysql._prepareFields(), { code: MySQLError.codes.INVALID_MODEL }); + + }); + + it('should return empty fields with no fields', async function() { + + const preparedFields = await mysql._prepareFields(dummyModel); + assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); + + }); + + it('should return the correct where and placeholders fields', async function() { + + const preparedFields = await mysql._prepareFields(dummyModel, { foo: 'bar' }); + + assert.deepEqual(preparedFields, expectedPrepareFields); + + }); + + it('should return LEFT JOIN query if Join Type field not exist', async function() { + + const fakeFields = { + foo: 'bar', + _joins: [ + { table: 'table_b', alias: 'tb', condition: 'foo' } + ] + }; + + const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + expectedPrepareFields.joins = ' LEFT JOIN dbname.table_b tb ON foo'; + + assert.deepEqual(preparedFields, expectedPrepareFields); + + expectedPrepareFields.joins = ''; + + }); + + it('should return empty join string if join type is \'\'', async function() { + + const fakeFields = { + foo: 'bar', + _joins: [ + { table: 'table_b', type: '', alias: 'tb', condition: 'foo' } + ] + }; + + const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + + assert.deepEqual(preparedFields, expectedPrepareFields); + + expectedPrepareFields.joins = ''; + + }); + + it('should return RIGHT JOIN query with join type is \'RIGHT\' ', async function() { + + const fakeFields = { + foo: 'bar', + _joins: [ + { table: 'table_b', type: 'RIGHT', alias: 'tb', condition: 'foo' } + ] + }; + + const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + expectedPrepareFields.joins = ' RIGHT JOIN dbname.table_b tb ON foo'; + + assert.deepEqual(preparedFields, expectedPrepareFields); + + expectedPrepareFields.joins = ''; + + }); + }); + + describe('Get Fields', function() { it('Should return formatted fields', async function() { - - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo', extra: 1 }]]); - + + validQueryManager.addValidQuery(`SHOW COLUMNS FROM ${fullTableName}`, [[{ Field: 'foo', extra: 1 }]]); + const fields = await mysql._getFields(dummyModel); - + assert.deepEqual(fields, { foo: { Field: 'foo', extra: 1 } }); - - assert.equal(stubCall.args[0][0], `SHOW COLUMNS FROM ${fullTableName}`); - - stubCall.restore(); - }); - - it('should return Error with no model', async() => { - + + validQueryManager.cleanQueries(); + + }); + + it('should return Error with no model', async function() { + await assert.rejects(mysql._getFields(), { code: MySQLError.codes.INVALID_MODEL }); - + }); + }); - }) -}); + describe('Build field query', function() { + it('Should build field query correctly for a single value field', function() { -/* Old Tests + const values = [['foo', 'test'], ['bar', 1]]; -let stubKnex; - let stubBuild; + for(const [field, value] of values) { + const rows = MySQL._buildFieldQuery(field, value); - let dummyModel; + assert.deepEqual(rows.where, [`${field} = :${field}`]); + assert.deepEqual(rows.placeholders, { [field]: value }); + } - let fullTableName; + }); - const mysql = new MySQL({}); + it('Should build field query correctly for a single value field with alias', function() { - class Model { - getTable() { - return 'table'; - } - } + const values = [['foo', 'test'], ['bar', 1]]; + const alias = 'test'; - const knexGetter = () => {}; + for(const [field, value] of values) { + const rows = MySQL._buildFieldQuery(field, value, alias); - before(() => { + assert.deepEqual(rows.where, [`${alias}.${field} = :${field}`]); + assert.deepEqual(rows.placeholders, { [field]: value }); + } - stubKnex = sinon.stub(MySQL.prototype, 'knex').get(() => knexGetter); + }); - stubBuild = sinon.stub(QueryBuilder.prototype, 'build').callsFake(() => { - return true; - }); + it('Should build field query correctly for a single value field with added suffix to placeholder', function() { - dummyModel = new Model(); - dummyModel.dbname = 'dbname'; + const values = [['foo', 'test'], ['bar', 1]]; - fullTableName = `${dummyModel.dbname}.${dummyModel.getTable()}`; - }); + let index = 0; + for(const [field, value] of values) { + const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); - after(() => { - stubKnex.restore(); - stubBuild.restore(); - }); + const ph = `${field}_${index}`; + assert.deepEqual(rows.where, [`${field} = :${ph}`]); + assert.deepEqual(rows.placeholders, { [ph]: value }); -describe('static getters', () => { - it('should return default limit', () => { - const DEFAULT_LIMIT = 500; + index++; + } - assert.equal(DEFAULT_LIMIT, MySQL.defaultLimit); - }); + }); - it('should return max iddle timeout', () => { - const MAX_IDDLE_TIMEOUT = 60 * 5; + it('Should build field query correctly for field with null value', function() { - assert.equal(MAX_IDDLE_TIMEOUT, MySQL.maxIddleTimeout); - }); + const values = [['foo', null], ['bar', null]]; - it('should return "_filters"', () => { - assert.equal('_filters', MySQL.filters); - }); + let index = 0; + for(const [field, value] of values) { + const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); - it('should return "_joins"', () => { - assert.equal('_joins', MySQL.joins); - }); + const ph = `${field}_${index}`; + assert.deepEqual(rows.where, [`${field} IS NULL`]); + assert.deepEqual(rows.placeholders, { [ph]: value }); - it('should return "_columns"', () => { - assert.equal('_columns', MySQL.columns); + index++; + } + + }); + + it('Should build field query correctly for field with multi values', function() { + + const res = MySQL._buildFieldQuery('foo', [1, '3']); + + assert.deepEqual(res.where, ['foo IN (:foo_0,:foo_1)']); + assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); + + }); + + it('Should build field query correctly for field with multi values including NULL', function() { + + const res = MySQL._buildFieldQuery('foo', [1, '3', null]); + assert.deepEqual(res.where, ['(foo IS NULL OR foo IN (:foo_0,:foo_1))']); + assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); + + }); }); }); - - - describe('save methods', function() { + describe('Save Methods', function() { let stubFields; let stubCall; @@ -322,7 +645,7 @@ describe('static getters', () => { assert.deepEqual(stubCall.args[0][1], { set_foo: 'bar', foo: 'barr' }); }); - it('if getFields not return id, date_created and date_modified', async() => { + it('if getFields not return id, date_created and date_modified', async function() { const fields2 = { some: 'some' }; stubFields.returns(fields2); @@ -344,12 +667,12 @@ describe('static getters', () => { describe('should throw', function() { - it('when attempting to save with no model', async() => { + it('when attempting to save with no model', async function() { await assert.rejects(mysql.save(), { code: MySQLError.codes.INVALID_MODEL }); }); - it('when attempting to update with no model', async() => { + it('when attempting to update with no model', async function() { await assert.rejects(mysql.update(), { code: MySQLError.codes.INVALID_MODEL }); }); @@ -365,17 +688,32 @@ describe('static getters', () => { }); }); - describe('get() getTotals()', function() { + describe('Get Methods', function() { + + const knexGetter = () => {}; - const stubExecute = result => sinon.stub(QueryBuilder.prototype, 'execute').callsFake(() => result); + let stubKnex; + let stubBuild; + + beforeEach(() => { + stubKnex = sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); + stubBuild = sandbox.stub(QueryBuilder.prototype, 'build').callsFake(() => { + return true; + }); + }); + + after(() => { + stubKnex.restore(); + stubBuild.restore(); + }); const testParams = (params, expectedParams) => { assert.deepEqual(params, expectedParams, 'shouldn\'t modify ofiginal params'); }; - it('Get Totals in empty Table, should return default values', async() => { + it('Get Totals in empty Table, should return default values', async function() { - const stub2 = sinon.stub(MySQL.prototype, 'get').callsFake(() => [{ count: 0 }]); + sandbox.stub(MySQL.prototype, 'get').callsFake(() => [{ count: 0 }]); const totalExpected = { page: 1, @@ -385,15 +723,13 @@ describe('static getters', () => { }; assert.deepEqual(await mysql.getTotals(dummyModel), totalExpected); - - stub2.restore(); }); it('Should return empty results and totals with zero values', async function() { const params = {}; - const stub = stubExecute([]); + sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => []); const result = await mysql.get(dummyModel, params); @@ -405,7 +741,7 @@ describe('static getters', () => { testParams(params, {}); - stub.restore(); + }); it('Should return results and totals only with filters', async function() { @@ -413,7 +749,7 @@ describe('static getters', () => { const originalParams = { someFilter: 'foo' }; const params = { ...originalParams }; - const stubResults = stubExecute([{ result: 1 }, { result: 2 }]); + const stubResults = sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ result: 1 }, { result: 2 }]); const result = await mysql.get(dummyModel, params); @@ -423,7 +759,7 @@ describe('static getters', () => { stubResults.restore(); - const stubTotals = stubExecute([{ count: 650 }]); + sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ count: 650 }]); const resultTotals = await mysql.getTotals(dummyModel); @@ -433,8 +769,6 @@ describe('static getters', () => { pageSize: 500, pages: 2 }); - - stubTotals.restore(); }); it('Should return results and totals', async function() { @@ -442,7 +776,7 @@ describe('static getters', () => { const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; const params = { ...originalParams }; - const stubResults = stubExecute([{ result: 1 }, { result: 2 }]); + const stubResults = sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ result: 1 }, { result: 2 }]); const result = await mysql.get(dummyModel, params); @@ -452,7 +786,7 @@ describe('static getters', () => { stubResults.restore(); - const stubTotals = stubExecute([{ count: 650 }]); + sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ count: 650 }]); const resultTotals = await mysql.getTotals(dummyModel); @@ -462,11 +796,9 @@ describe('static getters', () => { pageSize: 10, pages: 65 }); - - stubTotals.restore(); }); - it('Should throws Error when try to get with no model', async() => { + it('Should throws Error when try to get with no model', async function() { await assert.rejects(mysql.get(), { code: MySQLError.codes.INVALID_MODEL }); await assert.rejects(mysql.getTotals(), { code: MySQLError.codes.INVALID_MODEL }); @@ -474,32 +806,7 @@ describe('static getters', () => { }); }); - describe('getFields()', function() { - - it('Should return formatted fields', async function() { - - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo', extra: 1 }]]); - - const fields = await mysql._getFields(dummyModel); - - assert.deepEqual(fields, { - foo: { Field: 'foo', extra: 1 } - }); - - assert.equal(stubCall.args[0][0], `SHOW COLUMNS FROM ${fullTableName}`); - - stubCall.restore(); - }); - - it('should return Error with no model', async() => { - - await assert.rejects(mysql._getFields(), { code: MySQLError.codes.INVALID_MODEL }); - - }); - }); - - describe('remove methods', function() { + describe('Remove methods', function() { let stubFields; let stubCall; @@ -570,426 +877,4 @@ describe('static getters', () => { }); }); - describe('Build field query', function() { - - it('Should build field query correctly for a single value field', function() { - - const values = [['foo', 'test'], ['bar', 1]]; - - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value); - - assert.deepEqual(rows.where, [`${field} = :${field}`]); - assert.deepEqual(rows.placeholders, { [field]: value }); - } - - }); - - it('Should build field query correctly for a single value field with alias', function() { - - const values = [['foo', 'test'], ['bar', 1]]; - const alias = 'test'; - - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value, alias); - - assert.deepEqual(rows.where, [`${alias}.${field} = :${field}`]); - assert.deepEqual(rows.placeholders, { [field]: value }); - } - - }); - - it('Should build field query correctly for a single value field with added suffix to placeholder', function() { - - const values = [['foo', 'test'], ['bar', 1]]; - - let index = 0; - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); - - const ph = `${field}_${index}`; - assert.deepEqual(rows.where, [`${field} = :${ph}`]); - assert.deepEqual(rows.placeholders, { [ph]: value }); - - index++; - } - - }); - - it('Should build field query correctly for field with null value', function() { - - const values = [['foo', null], ['bar', null]]; - - let index = 0; - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); - - const ph = `${field}_${index}`; - assert.deepEqual(rows.where, [`${field} IS NULL`]); - assert.deepEqual(rows.placeholders, { [ph]: value }); - - index++; - } - - }); - - it('Should build field query correctly for field with multi values', function() { - - const res = MySQL._buildFieldQuery('foo', [1, '3']); - - assert.deepEqual(res.where, ['foo IN (:foo_0,:foo_1)']); - assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); - - }); - - it('Should build field query correctly for field with multi values including NULL', function() { - - const res = MySQL._buildFieldQuery('foo', [1, '3', null]); - assert.deepEqual(res.where, ['(foo IS NULL OR foo IN (:foo_0,:foo_1))']); - assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); - - }); - }); - - describe('Field Mapping', function() { - - const fieldsMap = { - order_form_id: ['orderFormId'], - check_boolean: ['checkBoolean'], - check_falsy: ['checkFalsy'], - check_multiple: ['checkMulti', 'checkMultiple'], - check_string: 'checkString', - some_field: ['other_field'] - }; - - - it('Should map correctly an object', function() { - - const data = { - orderFormId: 'yes', - checkBoolean: true, - checkFalsy: false, - status: false, - checkMultiple: true, - CapitalField: false, - no: 5, - noMap: 6, - checkString: 'string' - }; - - - const result = mysql._mapFields(data, fieldsMap); - - assert.equal(result.orderFormId, undefined); - assert.equal(result.checkBoolean, undefined); - assert.equal(result.checkFalsy, undefined); - assert.equal(result.checkMultiple, undefined); - assert.equal(result.checkString, undefined); - assert.equal(result.CapitalField, undefined); - - assert.equal(result.order_form_id, 'yes'); - assert.equal(result.check_boolean, true); - assert.equal(result.check_falsy, false); - assert.equal(result.check_multiple, true); - assert.equal(result.status, false); - assert.equal(result.capital_field, false); - assert.equal(result.check_string, 'string'); - assert.equal(result.no_map, 6); - assert.equal(result.no, 5); - - }); - - it('Should map correctly an array of objects ', function() { - - const data = [{ - orderFormId: 'yes', - checkBoolean: true, - checkFalsy: false, - status: false, - checkMultiple: true, - nomap: 5, - checkString: 'string' - }, { - orderFormId: 'yes', - checkBoolean: true, - checkFalsy: false, - status: false, - checkMultiple: true, - nomap: 5, - checkString: 'string' - }]; - - const results = mysql._mapFields(data, fieldsMap); - - results.forEach(result => { - assert.equal(result.orderFormId, undefined); - assert.equal(result.checkBoolean, undefined); - assert.equal(result.checkFalsy, undefined); - assert.equal(result.checkMultiple, undefined); - assert.equal(result.checkString, undefined); - - assert.equal(result.order_form_id, 'yes'); - assert.equal(result.check_boolean, true); - assert.equal(result.check_falsy, false); - assert.equal(result.check_multiple, true); - assert.equal(result.status, false); - assert.equal(result.check_string, 'string'); - assert.equal(result.nomap, 5); - }); - - }); - - it('Should leave symbols properties unchanged', function() { - - const data = [{ - [Symbol.for('mock')]: 'symbol', - nomap: 5 - }, { - [Symbol.for('mock')]: 'symbol', - nomap: 5 - }]; - - const results = mysql._mapFields(data, fieldsMap); - - results.forEach(result => { - assert.equal(result[Symbol.for('mock')], 'symbol'); - assert.equal(result.nomap, 5); - }); - - }); - }); - - describe('Prepare Fields', function() { - - const expectedPrepareFieldsEmpty = { - where: [], - placeholders: {}, - columns: ['*'], - joins: '' - }; - - const expectedPrepareFields = { - where: ['foo = :foo'], - placeholders: { foo: 'bar' }, - columns: ['*'], - joins: '' - }; - - it('should return Error with no model', async() => { - - await assert.rejects(mysql._prepareFields(), { code: MySQLError.codes.INVALID_MODEL }); - - }); - - it('should return empty fields with no fields', async() => { - - const preparedFields = await mysql._prepareFields(dummyModel); - assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); - - }); - - it('should return the correct where and placeholders fields', async() => { - - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo' }]]); - - const preparedFields = await mysql._prepareFields(dummyModel, { foo: 'bar' }); - - assert.deepEqual(preparedFields, expectedPrepareFields); - - stubCall.restore(); - - }); - - it('should return LEFT JOIN query if Join Type field not exist', async() => { - - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo' }]]); - - const fakeFields = { - foo: 'bar', - _joins: [ - { table: 'table_b', alias: 'tb', condition: 'foo' } - ] - }; - - const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); - expectedPrepareFields.joins = ' LEFT JOIN dbname.table_b tb ON foo'; - - assert.deepEqual(preparedFields, expectedPrepareFields); - - expectedPrepareFields.joins = ''; - stubCall.restore(); - - }); - - it('should return empty join string if join type is \'\'', async() => { - - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo' }]]); - - const fakeFields = { - foo: 'bar', - _joins: [ - { table: 'table_b', type: '', alias: 'tb', condition: 'foo' } - ] - }; - - const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); - - assert.deepEqual(preparedFields, expectedPrepareFields); - - expectedPrepareFields.joins = ''; - stubCall.restore(); - - }); - - it('should return RIGHT JOIN query with join type is \'RIGHT\' ', async() => { - - const stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([[{ Field: 'foo' }]]); - - const fakeFields = { - foo: 'bar', - _joins: [ - { table: 'table_b', type: 'RIGHT', alias: 'tb', condition: 'foo' } - ] - }; - - const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); - expectedPrepareFields.joins = ' RIGHT JOIN dbname.table_b tb ON foo'; - - assert.deepEqual(preparedFields, expectedPrepareFields); - - expectedPrepareFields.joins = ''; - stubCall.restore(); - - }); - }); - - describe('shouldDestroyConnectionPool', function() { - - it('should return true', function() { - - const now = Date.now() / 1000 | 0; - - const dates = [ - now - MySQL.maxIddleTimeout - 50 // 50 seconds after iddle timeout limit - ]; - - dates.forEach(lastActivity => { - assert(mysql._shouldDestroyConnectionPool(lastActivity)); - }); - - assert(mysql._shouldDestroyConnectionPool()); - }); - - it('should return false', function() { - - const now = Date.now() / 1000 | 0; - - const dates = [ - now - MySQL.maxIddleTimeout + 50 // 50 seconds before iddle timeout limit - ]; - - dates.forEach(lastActivity => { - assert(!mysql._shouldDestroyConnectionPool(lastActivity)); - }); - - }); - - }); - - describe('End Connection', function() { - it('should end the connection', function() { - //mysql.end(); - //mysql.closeIddleConnections(); - }) - }); - - describe('Calls', function() { - - const validQuery = 'SHOW COLUMNS FROM `table`'; - - const queryError = new Error('Some Query Error'); - queryError.errno = 1206; - queryError.code = 'Invalid Query'; - - const connection = { - config: {}, - query: async queryToResolve => { - if(queryToResolve !== validQuery) - throw queryError; - - return ([[{ Field: 'foo' }]]); - }, - release: () => true - }; - - // const poolStub = sinon.stub(MySQL.prototype, 'pool').get( () => ({ getConnection: () => ({connection}) })); - - it('should return positives results', async() => { - const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') - .returns(connection); - - const callQuery = await mysql._call(validQuery, {}); - - assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); - connectionStub.restore(); - }); - - it('should reconnect', async() => { - const connectionStub = sinon.stub(MySQL.prototype, 'getConnection'); - - connectionStub - .onCall(0) - .rejects(new MySQLError('Too Many Connections', MySQLError.codes.TOO_MANY_CONNECTION)); - - connectionStub - .onCall(1) - .returns(connection); - - const callQuery = await mysql._call(validQuery, {}); - - assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); - connectionStub.restore(); - }); - - it('should thrown MySQLError if query is wrong/invalid ', async() => { - const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') - .returns(connection); - - await assert.rejects(mysql._call('SOME INVALID QUERY', {}), { code: MySQLError.codes.INVALID_QUERY }); - - connectionStub.restore(); - }); - - - it('should return MySQLError from Database', async() => { - const connectionStub = sinon.stub(MySQL.prototype, 'getConnection') - .rejects(new MySQLError('Database Error', MySQLError.codes.CONNECTION_ERROR)); - - await assert.rejects(mysql._call(validQuery, {}), { code: MySQLError.codes.CONNECTION_ERROR }); - connectionStub.restore(); - }); - - }); - - describe('Query Format', function() { - - const queryString = value => `SHOW COLUMNS FROM ${value}`; - - it('should return the same Query String if value dont exist', () => { - assert.equal(mysql._queryFormat(queryString('`table`')), queryString('`table`')); - }); - - it('should return the Query String with correct value change', () => { - assert.equal(mysql._queryFormat(queryString(':foo'), { foo: 'bar' }), queryString('\'bar\'')); - }); - - it('should return the Query String with correct value format', () => { - assert.equal(mysql._queryFormat(queryString(':foo'), 'foo'), queryString(':foo')); - }); - }); - -*/ \ No newline at end of file +}); From 3c00ab08ed60500174568938bd1ca5218ac1139e Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Tue, 18 Jun 2019 15:00:30 -0300 Subject: [PATCH 18/35] Refactor Mysql module --- mysql/index.js | 9 - mysql/mysql-error.js | 28 -- mysql/mysql.js | 789 ------------------------------------------- 3 files changed, 826 deletions(-) delete mode 100644 mysql/index.js delete mode 100644 mysql/mysql-error.js delete mode 100644 mysql/mysql.js diff --git a/mysql/index.js b/mysql/index.js deleted file mode 100644 index 41d519d..0000000 --- a/mysql/index.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const MySQL = require('./mysql'); -const MySQLError = require('./mysql-error'); - -module.exports = { - MySQL, - MySQLError -}; diff --git a/mysql/mysql-error.js b/mysql/mysql-error.js deleted file mode 100644 index fbee18c..0000000 --- a/mysql/mysql-error.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -class MySQLError extends Error { - - static get codes() { - - return { - EMPTY_FIELDS: 1, - INVALID_STATEMENT: 2, - INVALID_MODEL: 3, - INVALID_DATA: 4, - INVALID_QUERY: 5, - TOO_MANY_CONNECTION: 6, - CONNECTION_ERROR: 7, - ALL_POOL_ENDED: 8 - }; - - } - - constructor(err, code) { - super(err); - this.message = err.message || err; - this.code = code; - this.name = 'MySQLError'; - } -} - -module.exports = MySQLError; diff --git a/mysql/mysql.js b/mysql/mysql.js deleted file mode 100644 index 8c11e86..0000000 --- a/mysql/mysql.js +++ /dev/null @@ -1,789 +0,0 @@ -'use strict'; - -const mysql = require('mysql2/promise'); -const { escape } = require('mysql2'); -const knex = require('knex'); - -const logger = require('@janiscommerce/logger'); -const QueryBuilder = require('@janiscommerce/query-builder'); - -const MySQLError = require('./mysql-error'); -const Utils = require('./../utils'); - -const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds -const CONNECTION_LIMIT = 10; - -const DEFAULT_LIMIT = 500; - -class MySQL { - - static get defaultLimit() { - return DEFAULT_LIMIT; - } - - static get maxIddleTimeout() { - return MAX_IDDLE_TIMEOUT; - } - - static get filters() { - return '_filters'; - } - - static get columns() { - return '_columns'; - } - - static get joins() { - return '_joins'; - } - - /** - * @returns {object} connection pool - */ - static get connectionPool() { - return this._connectionPool || {}; - } - - /** - * Set a connection pool - * @param {object} connection Pool connection data - */ - static set connectionPool(connection) { - - if(!this._connectionPool) - this._connectionPool = {}; - - this._connectionPool[connection.threadId] = { - connection, - lastActivity: (new Date() / 1000 | 0), - id: connection.threadId - }; - } - - /** - * Constructor - * @param {object} config Database configuration. - */ - constructor(config) { - this.config = { - host: config.host, - user: config.user, - password: config.password, - database: config.database || null, - port: config.port, - connectionLimit: config.connectionLimit || CONNECTION_LIMIT, - multipleStatements: true, - prefix: config.prefix || '' - }; - } - - /** - * Returns a MySQL pool, if it didn't exist create one. - * @returns {object} MySQL pool instance - */ - get pool() { - - if(!this._pool) - this._pool = mysql.createPool(this.config); - - return this._pool; - } - - /* istanbul ignore next */ - get knex() { - - if(!this._knex) { - this._knex = knex({ - client: 'mysql2', - version: '1.5.2', - connection: this.config, - pool: { min: 0, max: this.config.connectionLimit }, - wrapIdentifier: (value, origImpl) => origImpl(Utils.convertToSnakeCase(value)), - postProcessResponse: result => (Array.isArray(result) ? result.map(Utils.convertKeysToCamelCase) : Utils.convertKeysToCamelCase(result)) - }).on('query-error', error => { - logger.error('Query', error); - }); - } - - return this._knex; - } - - _queryFormat(query, values) { - - if(!values) - return query; - - return query.replace(/:(\w+)/g, (txt, key) => { - if(values.hasOwnProperty(key)) - return escape(values[key]); - - return txt; - }); - } - - /** - * Get the pool connection. - * @returns {Promise} Pool Promise Connection or MySQLError - */ - async getConnection() { - - try { - - const poolConnection = await this.pool.getConnection(); - poolConnection.connection.config.queryFormat = this._queryFormat.bind(this); - this.constructor.connectionPool = poolConnection.connection; - - return poolConnection; - - } catch(error) { - if(error.code === 'ER_CON_COUNT_ERROR') - throw new MySQLError(error.code, MySQLError.codes.TOO_MANY_CONNECTION); - - throw new MySQLError(error.code || 'Database Error', MySQLError.codes.CONNECTION_ERROR); - } - - } - - /** - * Map a field to it's DB column name - * @param {string} field - The field that will be mapped. - * @param {object} [map=this.fieldsMap] - The map - * @private - */ - _mapField(field, map) { - if(typeof field === 'symbol') - return field; - - let parsed = null; - - Object.entries(map || this.fieldsMap || {}).forEach(([key, value]) => { - - value = Array.isArray(value) ? value : [value]; - - if(value.includes(field)) - parsed = key; - }); - - if(!parsed) { - // Replace camel case to lower dash format. - // E.g: camelCase => camel_case; - // UpperCamelCase => upper_camel_case - - parsed = field.replace(/[A-Z]/g, (match, index) => (index !== 0 ? '_' : '') + match.toLowerCase()); - } - return parsed; - } - - /** - * Map each property to DB column name - * @param {object} item - The object that will be mapped - * @param {object} [map=this.fieldsMap] - The map - * @private - */ - _mapItem(item, map) { - const fields = {}; - item = typeof item === 'undefined' ? {} : item; - - // We use Reflect and not Object.entries/Keys - // because we want to keep Symbol properties - Reflect.ownKeys(item).forEach(key => { - const field = this._mapField(key, map); - fields[field || key] = item[key]; - }); - - return fields; - } - - /** - * Map each propety in the collection to a valid DB collection - * @param {object/array} data - An array of objects, or an object. - * @param {object} [map=this.fieldsMap] - The map - * - */ - _mapFields(data, map) { - if(Array.isArray(data)) - return data.map(item => this._mapItem(item, map)); - - return this._mapItem(data, map); - - } - - /** - * Execute de Query in the database - * @param {string} query Query to be executed, placeholders with :PARAM_NAME - * @param {object} placeholders Object with key PARAM_NAME, value PARAM_VALUE, Default empty - * @returns {Promise} Resolve: RowsAffected, Reject: MySQLError - */ - async _call(query, placeholders = {}) { - - try { - const connection = await this.getConnection(); - const rows = await connection.query(query, placeholders); - connection.release(); - return rows; - - } catch(error) { - // Connections Limit - if(error.code === MySQLError.codes.TOO_MANY_CONNECTION) { // Retry - const retryFunction = () => this._call.apply(this, arguments); - setTimeout(retryFunction, 1000); - return retryFunction(); - } - - // Other Connections Errors - if(error.code === MySQLError.codes.CONNECTION_ERROR) { - logger.error('Database', error.message); - throw error; - } - // Query Errors - logger.error('Query', error.errno, error.code, error.message); - logger.debug(query, placeholders); - throw new MySQLError(error.code, MySQLError.codes.INVALID_QUERY); - } - } - - /** - * Get the Fields from the model - * @param {class} model Model Class - */ - async _getFields(model) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - const table = model.getTable(); - const { dbname } = model; - - // _call returns an array with colummns in the first position and Schema information in the second - const [rows] = await this._call(`SHOW COLUMNS FROM ${dbname}.${table}`); - - const fields = {}; - - for(const field of rows) - fields[field.Field] = field; - - return fields; - } - - /** - * Generates joins string for queries - * @param {Array.<{table: String, type: String, alias: String, condition: String}>} joins - Array of shape [{ table, type, alias, condition }] - * @param {string} dbname Database Name - * @returns {string} - */ - _buildJoins(joins, dbname) { - if(!joins || !Array.isArray(joins)) - return ''; - - return joins.reduce((acc, { table, type = 'LEFT', alias, condition }) => { - if(!/^(LEFT|RIGHT|INNER)$/.test(type)) - return acc; - - return `${acc} ${type} JOIN ${dbname}.${table} ${alias} ON ${condition}`; - }, ''); - } - - /** - * Generate where and placeholders for a single field - * - * @param {string} field - The field - * @param {mixed} value - The field's value - * @return {object} { where, placeholders } - */ - static _buildFieldQuery(field, value, alias = '', suffix = '') { - - const where = []; - const placeholders = {}; - const fieldKey = `${alias ? alias + '.' : ''}${field}`; - - if(Array.isArray(value)) { - - const queryIn = []; - - const hasNull = value.some(item => item === null); - if(hasNull) - value = value.filter(item => item !== null); - - for(let i = 0; i < value.length; i++) { - - const key = `${field}_${i}${suffix}`; - - placeholders[key] = value[i]; - queryIn.push(':' + key); - - } - - if(hasNull) - where.push(`(${fieldKey} IS NULL OR ${fieldKey} IN (${queryIn.join(',')}))`); - else where.push(`${fieldKey} IN (${queryIn.join(',')})`); - - - } else { - - const key = `${field}${suffix}`; - - placeholders[key] = value; - - if(value === null) - where.push(`${fieldKey} IS NULL`); - else where.push(`${fieldKey} = :${key}`); - } - - return { where, placeholders }; - - } - - /** - * Generate Where and placeholders to query with fields - * - * @param {class} Model Class - * @param {object} fields The fields - * @param {string} tableAlias - * @param {string} suffix - * @return {object} { where and placeholders } - */ - async _prepareFields(model, fields = {}, tableAlias = '', suffix = '') { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - let where = []; - const placeholders = {}; - const { dbname } = model; - - const columns = fields[this.constructor.columns] || ['*']; - - const joins = this._buildJoins(fields[this.constructor.joins], dbname); - - if(!Object.keys(fields).length) { - return { - where, - placeholders, - columns, - joins - }; - } - - const tableFields = await this._getFields(model); - - fields = this._mapFields(fields); - - for(const [field, value] of Object.entries(fields)) { - - if(!tableFields[field] || value === undefined) - continue; - - const { where: w, placeholders: ph } = this.constructor._buildFieldQuery(field, value, tableAlias, suffix); - - where = [...where, ...w]; - - Object.assign(placeholders, ph); - } - - return { - where, - placeholders, - columns, - joins - }; - } - - /** - * Insert an item into the database - * @param {Class} model - Model Class - * @param {object} item - The item to insert - * @param {boolean} allowUpsert - If upsert is allowed - * @returns {number} Number of the ID inserted - */ - async insert(model, item, allowUpsert = false) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - const table = model.getTable(); - const { dbname } = model; - - const noUpdate = ['id', 'date_created']; - const fields = []; - const placeholders = {}; - const duplicateUpdate = []; - - const time = (Date.now() / 1000 | 0); - - const tableFields = await this._getFields(model); - - item = this._mapFields(item); - - if(!Object.keys(item).some(field => typeof tableFields[field] !== 'undefined')) - throw new MySQLError('Insert must have fields', MySQLError.codes.EMPTY_FIELDS); - - if(tableFields.date_created) - item.date_created = item.date_created || time; - - if(tableFields.date_modified) - item.date_modified = time; - - if(tableFields.id) - duplicateUpdate.push('id = LAST_INSERT_ID(id)'); - - Object.entries(item).forEach(([key, value]) => { - - if(!tableFields[key]) - return true; - - fields.push(key); - - placeholders[key] = key === 'date_modified' ? time : value; - - if(!noUpdate.includes(key)) - duplicateUpdate.push(`${key} = VALUES(${key})`); - - }); - - const duplicateStatement = `ON DUPLICATE KEY UPDATE - ${duplicateUpdate.join(',\n')}`; - - const statement = `INSERT INTO ${dbname}.${table} (${fields.join(', ')}) - VALUES (${fields.map(field => ':' + field).join(', ')}) - ${allowUpsert ? duplicateStatement : ''}`; - - const [rows] = await this._call(statement, placeholders); - - return rows.insertId; - } - - /** - * Update a row - * @param {object} item - The item to update - */ - async update(model, item) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - const table = model.getTable(); - const { dbname } = model; - - const placeholders = {}; - - const tableFields = await this._getFields(model); - - const fields = []; - - let where = []; - let wherePlaceholders = {}; - - item = this._mapFields(item); - - if(item[this.constructor.filters]) { - - const whereField = item[this.constructor.filters]; - - ({ where, placeholders: wherePlaceholders } = await this._prepareFields(model, whereField)); - - Object.assign(placeholders, wherePlaceholders); - } - - Object.entries(item).forEach(([key, value]) => { - - if(!tableFields[key]) - return true; - - const { Type: type } = tableFields[key]; - - if(type === 'datetime' && value === true) - return fields.push(`${key} = NOW()`); - - const placeholdersKey = `set_${key}`; - - fields.push(`${key} = :${placeholdersKey}`); - - placeholders[placeholdersKey] = value; - - }); - - if(!fields.length) - throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS) - - const clause = where.length ? `WHERE ${where.join(' AND ')}` : ''; - - const statement = `UPDATE ${dbname}.${table} SET ${fields.join(', ')} ${clause}`; - - const [rows] = await this._call(statement, placeholders); - - return rows.insertId; - } - - /** - * Save a new item in the SQL database - * @param {Class} model Model Class - * @param {object} item object to saved - * @returns {number} Number of the ID of the item - */ - async save(model, item) { - return this.insert(model, item, true); - } - - /** - * Search in the database. - * - * @param {Class} model Model Class - * @param {object} params object with the parametres to search. - * @returns {Promise} Array with the objects founds. - */ - async get(model, params = {}) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - const newParams = { ...params }; // necesario para no alterar params y no afectar a las keys de cache - - if(!newParams.totals) { - - if(!newParams.limit) - newParams.limit = this.constructor.defaultLimit; - - model.totalsParams = newParams; - - } else - delete newParams.totals; - - const queryBuilder = new QueryBuilder(this.knex, model, newParams); - - queryBuilder.build(); - - const result = await queryBuilder.execute(); - - model.lastQueryEmpty = !result.length; - - return result; - } - - /** - * Get the stadistics - * @param {class} model Model Class - * @returns {object} return Total of values, actual Page, Page Size and Total Pages - */ - async getTotals(model) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - if(model.lastQueryEmpty) - return { total: 0, pages: 0 }; - - const params = model.totalsParams || {}; - - const [result] = await this.get(model, { - ...params, - totals: true, - fields: false, - count: true, - page: 1, - limit: 1 - }); - - return { - total: result.count, - page: params.page ? params.page : 1, - pageSize: params.limit ? params.limit : this.constructor.defaultLimit, - pages: params.limit ? Math.ceil(result.count / params.limit) : 1 - }; - } - - /** - * Perform a multi insert - * @param {class} model Model Class - * @param {array} items - The items to insert - * @returns {boolean} True if success - */ - /* istanbul ignore next */ - async multiInsert(model, items) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - if(!items || !items.length) - throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS); - - const table = model.getTable(); - const { dbname } = model; - - const noUpdate = ['id', 'date_created']; - const placeholders = {}; - const duplicateUpdate = []; - const values = []; - - let fields; - - const tableFields = await this._getFields(model); - - if(tableFields.id) - duplicateUpdate.push('id = LAST_INSERT_ID(id)'); - - const time = Date.now() / 1000 | 0; - - for(let i = 0; i < items.length; i++) { - - const itemValues = []; - - items[i] = this._mapFields(items[i]); - - if(tableFields.date_created) - items[i].date_created = items[i].date_created || time; - - if(tableFields.date_modified) - items[i].date_modified = time; - - const keys = Object.keys(items[i]).sort(); - - if(i === 0) - fields = keys; - - for(const key of keys) { - - if(!tableFields[key]) - continue; - - const placeholdersKey = `${key}_${i}`; - - itemValues.push(`:${placeholdersKey}`); - - placeholders[placeholdersKey] = items[i][key]; - - if(i === 0 && !noUpdate.includes(key)) - duplicateUpdate.push(`${key} = VALUES(${key})`); - } - - if(!itemValues.length) - throw new MySQLError('Values cannot be empty', MySQLError.codes.INVALID_STATEMENT); - - values.push(`(${itemValues.join(',')})`); - - } - - const statement = `INSERT INTO ${dbname}.${table} (${fields.join(',')}) - VALUES ${values.join(',\n')} - ON DUPLICATE KEY UPDATE - ${duplicateUpdate.join(',\n')}`; - - const [rows] = await this._call(statement, placeholders); - - return !!rows.insertId; - } - - /** - * Remove by fields and value - * - * @param {object} model The model - * @param {object} fields The fields to use in where clause, invalid fields will be ignore - * @return {Promise} { response from database } - */ - async remove(model, fields) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - if(!Utils.isObject(fields) || Utils.isEmptyObject(fields)) - throw new MySQLError('Invalid fields', MySQLError.codes.INVALID_DATA); - - const table = model.getTable(); - const { dbname } = model; - - const { - placeholders, - where, - joins - } = await this._prepareFields(model, fields); - - const whereClause = `WHERE ${where.join(' AND ')}` // where.length ? `WHERE ${where.join(' AND ')}` : ''; - - const joinsClause = joins || ''; - - const statement = `DELETE FROM ${dbname}.${table} - ${whereClause} - ${joinsClause}`; - - return this._call(statement, placeholders); - } - - /** - * Ends connections - * @returns {Promise} If error, else nothing - */ - /* istanbul ignore next */ - end() { - - if(this.closeIddleConnectionsInterval) - clearInterval(this.closeIddleConnectionsInterval); - - if(!this._pool) - return; - - this.pool.end(err => { - // all connections in the pool have ended - if(err) - throw new MySQLError('All Pool Have Ended', MySQLError.codes.ALL_POOL_ENDED); - }); - } - - /** - * Returns if the Connection is innactive - * @param {number} lastActivity Date of the last Activity - * @returns {boolean} - */ - _shouldDestroyConnectionPool(lastActivity) { - return !lastActivity || ((Date.now() / 1000 | 0) - lastActivity > this.constructor.maxIddleTimeout); - } - - /** - * Check for Iddle Connections and ends it. - */ - /* istanbul ignore next */ - closeIddleConnections() { - - if(this.closeIddleConnectionsInterval) - return false; - - this.closeIddleConnectionsInterval = setInterval(() => { - - if(Utils.isEmptyObject(this.constructor.connectionPool)) - return; - - Object.values(this.constructor.connectionPool) - .forEach(connectionPool => { - - if(connectionPool.connection && - this._shouldDestroyConnectionPool(connectionPool.lastActivity)) { - - logger.info(`Destroying connection: ${connectionPool.id}`); - - try { - - connectionPool.connection.destroy(); - - } catch(e) { - // Just in case. - } - - delete this.constructor.connectionPool[connectionPool.id]; - } - }); - - }, 5000); - - return true; - } - - /** - * No need to create indexes in this Database - */ - /* istanbul ignore next */ - async createIndexes() { - return true; - } - - -} - -module.exports = MySQL; From afeff5dc4144246acefc91bfae5851d88fe94539 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Tue, 18 Jun 2019 15:00:47 -0300 Subject: [PATCH 19/35] Refactor Mysql module --- lib/index.js | 9 ++ lib/mysql-error.js | 29 ++++ lib/mysql.js | 347 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 lib/index.js create mode 100644 lib/mysql-error.js create mode 100644 lib/mysql.js diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..41d519d --- /dev/null +++ b/lib/index.js @@ -0,0 +1,9 @@ +'use strict'; + +const MySQL = require('./mysql'); +const MySQLError = require('./mysql-error'); + +module.exports = { + MySQL, + MySQLError +}; diff --git a/lib/mysql-error.js b/lib/mysql-error.js new file mode 100644 index 0000000..3aeba47 --- /dev/null +++ b/lib/mysql-error.js @@ -0,0 +1,29 @@ +'use strict'; + +class MySQLError extends Error { + + static get codes() { + + return { + EMPTY_FIELDS: 1, + INVALID_STATEMENT: 2, + INVALID_MODEL: 3, + INVALID_DATA: 4, + INVALID_QUERY: 5, + TOO_MANY_CONNECTION: 6, + CONNECTION_ERROR: 7, + ALL_POOL_ENDED: 8, + EMPTY_ITEMS: 9 + }; + + } + + constructor(err, code) { + super(err); + this.message = err.message || err; + this.code = code; + this.name = 'MySQLError'; + } +} + +module.exports = MySQLError; diff --git a/lib/mysql.js b/lib/mysql.js new file mode 100644 index 0000000..b521d5f --- /dev/null +++ b/lib/mysql.js @@ -0,0 +1,347 @@ +'use strict'; + +const knex = require('knex'); + +const logger = require('@janiscommerce/logger'); +const QueryBuilder = require('@janiscommerce/query-builder'); + +const MySQLError = require('./mysql-error'); +const Utils = require('../utils'); + +const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds +const CONNECTION_LIMIT = 10; + +const DEFAULT_LIMIT = 500; + +class MySQL { + + static get defaultLimit() { + return DEFAULT_LIMIT; + } + + static get maxIddleTimeout() { + return MAX_IDDLE_TIMEOUT; + } + + static get filters() { + return '_filters'; + } + + static get columns() { + return '_columns'; + } + + static get joins() { + return '_joins'; + } + + /** + * @returns {object} connection pool + */ + static get connectionPool() { + return this._connectionPool || {}; + } + + /** + * Set a connection pool + * @param {object} connection Pool connection data + */ + static set connectionPool(connection) { + + if(!this._connectionPool) + this._connectionPool = {}; + + this._connectionPool[connection.threadId] = { + connection, + lastActivity: (new Date() / 1000 | 0), + id: connection.threadId + }; + } + + /** + * Constructor + * @param {object} config Database configuration. + */ + constructor(config) { + this.config = { + host: config.host, + user: config.user, + password: config.password, + database: config.database || null, + port: config.port, + connectionLimit: config.connectionLimit || CONNECTION_LIMIT, + multipleStatements: true, + prefix: config.prefix || '' + }; + } + + /* istanbul ignore next */ + get knex() { + + if(!this._knex) { + this._knex = knex({ + client: 'mysql2', + version: '1.5.2', + connection: this.config, + pool: { min: 0, max: this.config.connectionLimit }, + wrapIdentifier: (value, origImpl) => origImpl(Utils.convertToSnakeCase(value)), + postProcessResponse: result => (Array.isArray(result) ? result.map(Utils.convertKeysToCamelCase) : Utils.convertKeysToCamelCase(result)) + }).on('query-error', error => { + logger.error('Query', error); + }); + } + + return this._knex; + } + + // ******************************************************************************************************************** + + + /** + * Insert an item into the database + * @param {Class} model - Model Class + * @param {object} item - The item to insert + * @param {boolean} allowUpsert - If upsert is allowed + * @returns {number} Number of the ID inserted + */ + async insert(model, item) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!item || !Object.keys(item).length) + throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); + + const queryBuilder = new QueryBuilder(this.knex, model); + const result = await queryBuilder.insert(item); + return result; + } + + /** + * Update a row + * @param {object} params - The item to update + */ + async update(model, params) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!params || !Object.keys(params).length) + throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); + + const filters = params[this.constructor.filters]; + + delete params[this.constructor.filters]; + + const queryBuilder = new QueryBuilder(this.knex, model); + const result = await queryBuilder.update(params, filters); + return result; + } + + /** + * Save a new item in the SQL database + * @param {Class} model Model Class + * @param {object} item object to saved + * @returns {number} Number of the ID of the item + */ + async save(model, item) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!item || !Object.keys(item).length) + throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); + + const queryBuilder = new QueryBuilder(this.knex, model); + const result = await queryBuilder.save(item); + return result; + } + + /** + * Search in the database. + * + * @param {Class} model Model Class + * @param {object} params object with the parametres to search. + * @returns {Promise} Array with the objects founds. + */ + async get(model, params = {}) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + const newParams = { ...params }; // necesario para no alterar params y no afectar a las keys de cache + + if(!newParams.totals) { + + if(!newParams.limit) + newParams.limit = this.constructor.defaultLimit; + + model.totalsParams = newParams; + + } else + delete newParams.totals; + + const queryBuilder = new QueryBuilder(this.knex, model); + + const result = await queryBuilder.get(newParams); + + model.lastQueryEmpty = !result.length; + + return result; + } + + /** + * Get the stadistics + * @param {class} model Model Class + * @returns {object} return Total of values, actual Page, Page Size and Total Pages + */ + async getTotals(model) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(model.lastQueryEmpty) + return { total: 0, pages: 0 }; + + const params = model.totalsParams || {}; + + const [result] = await this.get(model, { + ...params, + totals: true, + fields: false, + count: true, + page: 1, + limit: 1 + }); + + return { + total: result.count, + page: params.page ? params.page : 1, + pageSize: params.limit ? params.limit : this.constructor.defaultLimit, + pages: params.limit ? Math.ceil(result.count / params.limit) : 1 + }; + } + + /** + * Perform a multi insert + * @param {class} model Model Class + * @param {array} items - The items to insert + * @returns {boolean} True if success + */ + /* istanbul ignore next */ + async multiInsert(model, items) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!items || !items.length) + throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS); + + const queryBuilder = new QueryBuilder(this.knex, model); + const result = await queryBuilder.save(items); + return result; + } + + /** + * Remove by fields and value + * + * @param {object} model The model + * @param {object} fields The fields to use in where clause, invalid fields will be ignore + * @return {Promise} { response from database } + */ + async remove(model, fields) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!Utils.isObject(fields) || Utils.isEmptyObject(fields)) + throw new MySQLError('Invalid fields', MySQLError.codes.INVALID_DATA); + + const filters = fields[this.constructor.filters]; + const joins = fields[this.constructor.joins]; + + const queryBuilder = new QueryBuilder(this.knex, model); + const result = await queryBuilder.remove(filters, joins); + return result; + } + + /** + * Ends connections + * @returns {Promise} If error, else nothing + */ + /* istanbul ignore next */ + end() { + + if(this.closeIddleConnectionsInterval) + clearInterval(this.closeIddleConnectionsInterval); + + if(!this._pool) + return; + + this.pool.end(err => { + // all connections in the pool have ended + if(err) + throw new MySQLError('All Pool Have Ended', MySQLError.codes.ALL_POOL_ENDED); + }); + } + + /** + * Returns if the Connection is innactive + * @param {number} lastActivity Date of the last Activity + * @returns {boolean} + */ + _shouldDestroyConnectionPool(lastActivity) { + return !lastActivity || ((Date.now() / 1000 | 0) - lastActivity > this.constructor.maxIddleTimeout); + } + + /** + * Check for Iddle Connections and ends it. + */ + /* istanbul ignore next */ + closeIddleConnections() { + + if(this.closeIddleConnectionsInterval) + return false; + + this.closeIddleConnectionsInterval = setInterval(() => { + + if(Utils.isEmptyObject(this.constructor.connectionPool)) + return; + + Object.values(this.constructor.connectionPool) + .forEach(connectionPool => { + + if(connectionPool.connection && + this._shouldDestroyConnectionPool(connectionPool.lastActivity)) { + + logger.info(`Destroying connection: ${connectionPool.id}`); + + try { + + connectionPool.connection.destroy(); + + } catch(e) { + // Just in case. + } + + delete this.constructor.connectionPool[connectionPool.id]; + } + }); + + }, 5000); + + return true; + } + + /** + * No need to create indexes in this Database + */ + /* istanbul ignore next */ + async createIndexes() { + return true; + } + + +} + +module.exports = MySQL; From 78a420cd1b474f1a7f9359c5eb1e0322e2a2481a Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Wed, 19 Jun 2019 18:38:53 -0300 Subject: [PATCH 20/35] Refactor module with new Query Builder --- .eslintrc.js | 6 +- .huskyrc.json | 5 + CHANGELOG.md | 11 +- index.js | 2 +- lib/mysql.js | 227 +++++-------- mocks/mysql-mock.js | 2 +- package.json | 9 +- tests/mysql-test.js | 788 +++++++++----------------------------------- utils/index.js | 9 +- 9 files changed, 274 insertions(+), 785 deletions(-) create mode 100644 .huskyrc.json diff --git a/.eslintrc.js b/.eslintrc.js index 06f4a93..a5e4eda 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -69,8 +69,10 @@ module.exports = { comments: 200 }], - 'spaced-comment': ['error', 'always', { - exceptions: ['*'] + 'space-before-function-paren': ['error', { + 'anonymous': 'never', + 'named': 'never', + 'asyncArrow': 'always' }], 'newline-per-chained-call': ['error', { diff --git a/.huskyrc.json b/.huskyrc.json new file mode 100644 index 0000000..8047f11 --- /dev/null +++ b/.huskyrc.json @@ -0,0 +1,5 @@ +{ + "hooks": { + "pre-commit": "npm run lint && npm t" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e9a4b..5766d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,5 +13,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Tests ## [Unreleased] +### Added +- *"lib/"* folder into package.json files + +### Changed +- Changed modules files folder into *"lib/"* +- Use `Query Builder` Insert, Save, Update, Remove new functions. +- + ### Removed -- `Query Builder` moved to an independent package \ No newline at end of file +- `Query Builder` moved to an independent package +- `end` function. \ No newline at end of file diff --git a/index.js b/index.js index f300e7f..c54c499 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ 'use strict'; -const { MySQL } = require('./mysql'); +const { MySQL } = require('./lib'); module.exports = MySQL; diff --git a/lib/mysql.js b/lib/mysql.js index b521d5f..d0d94df 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -23,41 +23,6 @@ class MySQL { return MAX_IDDLE_TIMEOUT; } - static get filters() { - return '_filters'; - } - - static get columns() { - return '_columns'; - } - - static get joins() { - return '_joins'; - } - - /** - * @returns {object} connection pool - */ - static get connectionPool() { - return this._connectionPool || {}; - } - - /** - * Set a connection pool - * @param {object} connection Pool connection data - */ - static set connectionPool(connection) { - - if(!this._connectionPool) - this._connectionPool = {}; - - this._connectionPool[connection.threadId] = { - connection, - lastActivity: (new Date() / 1000 | 0), - id: connection.threadId - }; - } - /** * Constructor * @param {object} config Database configuration. @@ -87,22 +52,18 @@ class MySQL { wrapIdentifier: (value, origImpl) => origImpl(Utils.convertToSnakeCase(value)), postProcessResponse: result => (Array.isArray(result) ? result.map(Utils.convertKeysToCamelCase) : Utils.convertKeysToCamelCase(result)) }).on('query-error', error => { - logger.error('Query', error); + logger.error('Query ', error.message); }); } return this._knex; } - // ******************************************************************************************************************** - - /** * Insert an item into the database * @param {Class} model - Model Class * @param {object} item - The item to insert - * @param {boolean} allowUpsert - If upsert is allowed - * @returns {number} Number of the ID inserted + * @returns {Promise} True if success */ async insert(model, item) { @@ -112,57 +73,76 @@ class MySQL { if(!item || !Object.keys(item).length) throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); - const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.insert(item); - return result; + try { + + const queryBuilder = new QueryBuilder(this.knex, model); + const [result] = await queryBuilder.insert(item); + // if Insert is valid in MySQL return [0] + return result === 0; + + } catch(error) { + return false; + } + } /** - * Update a row - * @param {object} params - The item to update - */ - async update(model, params) { + * Save a new item in the SQL database + * @param {Class} model Model Class + * @param {object} item object to saved + * @returns {Promise} True if success + */ + async save(model, item) { if(!model) throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - if(!params || !Object.keys(params).length) + if(!item || !Object.keys(item).length) throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); - const filters = params[this.constructor.filters]; + try { + const queryBuilder = new QueryBuilder(this.knex, model); + const [result] = await queryBuilder.save(item); - delete params[this.constructor.filters]; + return !!result.affectedRows; - const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.update(params, filters); - return result; + } catch(error) { + return false; + } } /** - * Save a new item in the SQL database - * @param {Class} model Model Class - * @param {object} item object to saved - * @returns {number} Number of the ID of the item - */ - async save(model, item) { + * Update a row + * @param {object} params - The value to change and the filters + * @returns {Promise} number of rows updated + */ + async update(model, params) { if(!model) throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - if(!item || !Object.keys(item).length) + if(!params || !Object.keys(params).length) throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); - const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.save(item); - return result; + const { filters } = params; + delete params.filters; + + try { + const queryBuilder = new QueryBuilder(this.knex, model); + return queryBuilder.update(params, filters); + + } catch(error) { + return 0; + } } + /** * Search in the database. * * @param {Class} model Model Class * @param {object} params object with the parametres to search. - * @returns {Promise} Array with the objects founds. + * @returns {Promise} Array with the objects founds. */ async get(model, params = {}) { @@ -181,19 +161,23 @@ class MySQL { } else delete newParams.totals; - const queryBuilder = new QueryBuilder(this.knex, model); + try { + const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.get(newParams); + const result = await queryBuilder.get(newParams); + model.lastQueryEmpty = !result.length; - model.lastQueryEmpty = !result.length; + return result; - return result; + } catch(error) { + return []; + } } /** * Get the stadistics * @param {class} model Model Class - * @returns {object} return Total of values, actual Page, Page Size and Total Pages + * @returns {Promise} return Total of values, actual Page, Page Size and Total Pages */ async getTotals(model) { @@ -226,7 +210,7 @@ class MySQL { * Perform a multi insert * @param {class} model Model Class * @param {array} items - The items to insert - * @returns {boolean} True if success + * @returns {Promise} number of affected rows */ /* istanbul ignore next */ async multiInsert(model, items) { @@ -237,100 +221,47 @@ class MySQL { if(!items || !items.length) throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS); - const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.save(items); - return result; + try { + const queryBuilder = new QueryBuilder(this.knex, model); + const [result] = await queryBuilder.save(items); + + return result.affectedRows; + + } catch(error) { + return 0; + } } /** * Remove by fields and value * * @param {object} model The model - * @param {object} fields The fields to use in where clause, invalid fields will be ignore - * @return {Promise} { response from database } + * @param {object} params The fields to use in where clause, invalid fields will be ignore + * @return {Promise} number of rows deleted */ - async remove(model, fields) { + async remove(model, params) { if(!model) throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - if(!Utils.isObject(fields) || Utils.isEmptyObject(fields)) + if(!Utils.isObject(params) || Utils.isEmptyObject(params)) throw new MySQLError('Invalid fields', MySQLError.codes.INVALID_DATA); - const filters = fields[this.constructor.filters]; - const joins = fields[this.constructor.joins]; - - const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.remove(filters, joins); - return result; - } - - /** - * Ends connections - * @returns {Promise} If error, else nothing - */ - /* istanbul ignore next */ - end() { - - if(this.closeIddleConnectionsInterval) - clearInterval(this.closeIddleConnectionsInterval); - - if(!this._pool) - return; - - this.pool.end(err => { - // all connections in the pool have ended - if(err) - throw new MySQLError('All Pool Have Ended', MySQLError.codes.ALL_POOL_ENDED); - }); - } - - /** - * Returns if the Connection is innactive - * @param {number} lastActivity Date of the last Activity - * @returns {boolean} - */ - _shouldDestroyConnectionPool(lastActivity) { - return !lastActivity || ((Date.now() / 1000 | 0) - lastActivity > this.constructor.maxIddleTimeout); - } - - /** - * Check for Iddle Connections and ends it. - */ - /* istanbul ignore next */ - closeIddleConnections() { - - if(this.closeIddleConnectionsInterval) - return false; + const { filters, joins } = params; - this.closeIddleConnectionsInterval = setInterval(() => { + delete params.filters; + delete params.joins; - if(Utils.isEmptyObject(this.constructor.connectionPool)) - return; + try { + const queryBuilder = new QueryBuilder(this.knex, model); + const [result] = await queryBuilder.remove(filters, joins); - Object.values(this.constructor.connectionPool) - .forEach(connectionPool => { + return result.affectedRows; - if(connectionPool.connection && - this._shouldDestroyConnectionPool(connectionPool.lastActivity)) { - - logger.info(`Destroying connection: ${connectionPool.id}`); - - try { - - connectionPool.connection.destroy(); - - } catch(e) { - // Just in case. - } - - delete this.constructor.connectionPool[connectionPool.id]; - } - }); - - }, 5000); + } catch(error) { + return 0; + } - return true; } /** @@ -340,8 +271,6 @@ class MySQL { async createIndexes() { return true; } - - } module.exports = MySQL; diff --git a/mocks/mysql-mock.js b/mocks/mysql-mock.js index f85fd96..6ca7e51 100644 --- a/mocks/mysql-mock.js +++ b/mocks/mysql-mock.js @@ -35,7 +35,7 @@ const validQueryManager = { MySQL.createPool = () => { return ({ - getConnection: async() => ({ + getConnection: async () => ({ connection: { config: {} }, diff --git a/package.json b/package.json index c6b98ce..b99a52e 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "", "main": "index.js", "scripts": { + "lint": "eslint index.js lib/ tests/", "test": "export TEST_ENV=true; mocha --exit -R nyan --recursive tests/", "watch-test": "export TEST_ENV=true; mocha --exit -R nyan -w --recursive tests/", "test-ci": "nyc --reporter=html --reporter=text mocha --recursive tests/", @@ -32,6 +33,10 @@ "mocha": "^5.2.0", "mock-require": "^3.0.3", "nyc": "^14.1.0", - "sinon": "^7.3.2" - } + "sinon": "^7.3.2", + "husky": "^2.4.1" + }, + "files": [ + "lib/" + ] } diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 019563a..7a9583b 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -2,30 +2,32 @@ const assert = require('assert'); const sinon = require('sinon'); -const mock = require('mock-require'); const QueryBuilder = require('@janiscommerce/query-builder'); -const { MySQL: mysql2Mock, validQueryManager } = require('../mocks/mysql-mock'); - -mock('mysql2/promise', mysql2Mock); - -const { MySQLError } = require('./../mysql'); +const { MySQLError } = require('./../lib'); const MySQL = require('./../index'); /* eslint-disable prefer-arrow-callback */ const sandbox = sinon.createSandbox(); -const sanitizeQuery = string => string.replace(/[\n\t]/g, ' ') - .replace(/\s+/g, ' ') - .trim(); - describe('MySQL module', function() { class Model { getTable() { return 'table'; } + + addDbName(t) { + return t; + } + + static get fields() { + return { + id: true, + superhero: true + }; + } } const mysql = new MySQL({}); @@ -33,678 +35,237 @@ describe('MySQL module', function() { const dummyModel = new Model(); dummyModel.dbname = 'dbname'; - const fullTableName = `${dummyModel.dbname}.${dummyModel.getTable()}`; - after(() => { sandbox.restore(); - mock.stopAll(); }); + describe('Save Methods', function() { - afterEach(() => { - sandbox.restore(); - validQueryManager.cleanQueries(); - }); - - describe('Connections Test', function() { - - describe('ConnecionPool - setter and getter', function() { - - it('Should return empty object when no pool connection was set', () => { - - assert(typeof MySQL.connectionPool, 'Object'); - assert.deepEqual({}, MySQL.connectionPool, 'it should by {}'); - }); - - it('Should set a connection', function() { - - const threadId = 123; - MySQL.connectionPool = { threadId }; - - assert(typeof MySQL.connectionPool, 'Object'); - assert(typeof MySQL.connectionPool[threadId], 'Object'); - assert(typeof MySQL.connectionPool[threadId].lastActivity, 'number'); - - const now = Date.now() / 1000 | 0; - - assert(MySQL.connectionPool[threadId].lastActivity >= now); - assert.equal(MySQL.connectionPool[threadId].id, threadId); - - }); - - }); - - describe('Pool Getter', function() { - - it('\'_pool\' field must be \'undefined\' before get \'pool\'', function() { - - assert.equal(typeof mysql._pool, 'undefined'); - assert.equal(typeof mysql.pool, 'object'); - - }); - - it('After get \'pool\' must be Pool must be setted and be equal', function() { - - assert.equal(typeof mysql.pool, 'object'); - assert.deepEqual(mysql.pool, mysql._pool); - - }); - - }); - - describe('Getting Connection', function() { - - it('should connect and add QueryFormat', async function() { - const poolConnection = await mysql.getConnection(); - - assert(typeof poolConnection, 'object'); - assert(poolConnection.connection.config.queryFormat); - }); - - it('should get Error when connection is not working', async function() { - - sandbox.stub(MySQL.prototype, 'pool') - .get(() => { throw new Error('Database Error'); }); - - await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.CONNECTION_ERROR }); - }); - - it('should get MySqlError when Pool have too many connection', async function() { - - const SqlErrorTooManyConnection = new Error('Too Many Connection'); - SqlErrorTooManyConnection.code = 'ER_CON_COUNT_ERROR'; - - sandbox.stub(MySQL.prototype, 'pool') - .get(() => { throw SqlErrorTooManyConnection; }); - - await assert.rejects(mysql.getConnection(), { code: MySQLError.codes.TOO_MANY_CONNECTION }); - - }); - - }); - - describe('Making Calls', function() { - - const validQuery = `SHOW COLUMNS FROM ${fullTableName}`; - - const queryError = new Error('Some Query Error'); - queryError.errno = 1206; - queryError.code = 'Invalid Query'; - - const connection = { - config: {}, - query: async queryToResolve => { - if(queryToResolve !== validQuery) - throw queryError; - - return ([[{ Field: 'foo' }]]); - }, - release: () => true - }; - - it('should return positives results', async() => { - validQueryManager.addValidQuery(validQuery, [[{ Field: 'foo' }]]); - - const callQuery = await mysql._call(validQuery, {}); - - assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); - }); - - it('should thrown MySQLError if query is wrong/invalid ', async() => { + context('when attempting to save/insert/update with valid item and fields', function() { - await assert.rejects(mysql._call('SOME INVALID QUERY', {}), { code: MySQLError.codes.INVALID_QUERY }); + beforeEach(() => { + const knexGetter = { raw: () => true }; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); }); - it('should return MySQLError from Database', async() => { - sandbox.stub(MySQL.prototype, 'getConnection') - .rejects(new MySQLError('Database Error', MySQLError.codes.CONNECTION_ERROR)); - - await assert.rejects(mysql._call(validQuery, {}), { code: MySQLError.codes.CONNECTION_ERROR }); + afterEach(() => { + sandbox.restore(); }); - it('should reconnect', async() => { + it('should return true if try to insert a new Item', async function() { - const connectionStub = sandbox.stub(MySQL.prototype, 'getConnection'); - - connectionStub - .onCall(0) - .rejects(new MySQLError('Too Many Connections', MySQLError.codes.TOO_MANY_CONNECTION)); - - connectionStub - .onCall(1) - .returns(connection); - - const callQuery = await mysql._call(validQuery, {}); - - assert.deepEqual(callQuery, [[{ Field: 'foo' }]]); - }); - - }); - - describe('Should Destroy Connection Pool', function() { - - it('should return true', function() { - - const now = Date.now() / 1000 | 0; - - const dates = [ - now - MySQL.maxIddleTimeout - 50 // 50 seconds after iddle timeout limit - ]; - - dates.forEach(lastActivity => { - assert(mysql._shouldDestroyConnectionPool(lastActivity)); + sandbox.stub(QueryBuilder.prototype, 'insert').callsFake(() => { + return [0]; }); - assert(mysql._shouldDestroyConnectionPool()); - }); - - it('should return false', function() { - - const now = Date.now() / 1000 | 0; - - const dates = [ - now - MySQL.maxIddleTimeout + 50 // 50 seconds before iddle timeout limit - ]; - - dates.forEach(lastActivity => { - assert(!mysql._shouldDestroyConnectionPool(lastActivity)); + const result = await mysql.insert(dummyModel, { + id: 1, + superhero: 'superman' }); + assert.equal(result, true); }); - }); - - }); - - describe('Field Preparations', function() { - - describe('Query Format', function() { - - const queryString = value => `SHOW COLUMNS FROM ${value}`; - - it('should return the same Query String if value dont exist', function() { - assert.equal(mysql._queryFormat(queryString('`table`')), queryString('`table`')); - }); - - it('should return the Query String with correct value change', function() { - assert.equal(mysql._queryFormat(queryString(':foo'), { foo: 'bar' }), queryString('\'bar\'')); - }); - - it('should return the Query String with correct value format', function() { - assert.equal(mysql._queryFormat(queryString(':foo'), 'foo'), queryString(':foo')); - }); - }); - - describe('Field Mapping', function() { - - const fieldsMap = { - order_form_id: ['orderFormId'], - check_boolean: ['checkBoolean'], - check_falsy: ['checkFalsy'], - check_multiple: ['checkMulti', 'checkMultiple'], - check_string: 'checkString', - some_field: ['other_field'] - }; - - it('Should not map anything and get empty object', function() { - assert.deepEqual(mysql._mapItem(), {}); - }); + it('should return false if try to insert an item that already exist', async function() { - it('Should map correctly an object', function() { - - const data = { - orderFormId: 'yes', - checkBoolean: true, - checkFalsy: false, - status: false, - checkMultiple: true, - CapitalField: false, - no: 5, - noMap: 6, - checkString: 'string' - }; - - - const result = mysql._mapFields(data, fieldsMap); - - assert.equal(result.orderFormId, undefined); - assert.equal(result.checkBoolean, undefined); - assert.equal(result.checkFalsy, undefined); - assert.equal(result.checkMultiple, undefined); - assert.equal(result.checkString, undefined); - assert.equal(result.CapitalField, undefined); - - assert.equal(result.order_form_id, 'yes'); - assert.equal(result.check_boolean, true); - assert.equal(result.check_falsy, false); - assert.equal(result.check_multiple, true); - assert.equal(result.status, false); - assert.equal(result.capital_field, false); - assert.equal(result.check_string, 'string'); - assert.equal(result.no_map, 6); - assert.equal(result.no, 5); + sandbox.stub(QueryBuilder.prototype, 'insert').rejects(); - }); - - it('Should map correctly an array of objects ', function() { - - const data = [{ - orderFormId: 'yes', - checkBoolean: true, - checkFalsy: false, - status: false, - checkMultiple: true, - nomap: 5, - checkString: 'string' - }, { - orderFormId: 'yes', - checkBoolean: true, - checkFalsy: false, - status: false, - checkMultiple: true, - nomap: 5, - checkString: 'string' - }]; - - const results = mysql._mapFields(data, fieldsMap); - - results.forEach(result => { - assert.equal(result.orderFormId, undefined); - assert.equal(result.checkBoolean, undefined); - assert.equal(result.checkFalsy, undefined); - assert.equal(result.checkMultiple, undefined); - assert.equal(result.checkString, undefined); - - assert.equal(result.order_form_id, 'yes'); - assert.equal(result.check_boolean, true); - assert.equal(result.check_falsy, false); - assert.equal(result.check_multiple, true); - assert.equal(result.status, false); - assert.equal(result.check_string, 'string'); - assert.equal(result.nomap, 5); + const result = await mysql.insert(dummyModel, { + id: 1, + superhero: 'superman' }); + assert.equal(result, false); }); - it('Should leave symbols properties unchanged', function() { - - const data = [{ - [Symbol.for('mock')]: 'symbol', - nomap: 5 - }, { - [Symbol.for('mock')]: 'symbol', - nomap: 5 - }]; + it('should return true if try to save a new item', async function() { - const results = mysql._mapFields(data, fieldsMap); + sandbox.stub(QueryBuilder.prototype, 'save').callsFake(() => { + return [{ affectedRows: 1, insertId: 0 }]; + }); - results.forEach(result => { - assert.equal(result[Symbol.for('mock')], 'symbol'); - assert.equal(result.nomap, 5); + const result = await mysql.save(dummyModel, { + id: 2, + superhero: 'batman' }); + assert.equal(result, true); }); - }); - describe('Prepare Fields', function() { - - const expectedPrepareFieldsEmpty = { - where: [], - placeholders: {}, - columns: ['*'], - joins: '' - }; - - const expectedPrepareFields = { - where: ['foo = :foo'], - placeholders: { foo: 'bar' }, - columns: ['*'], - joins: '' - }; - - beforeEach(() => { - validQueryManager.addValidQuery(`SHOW COLUMNS FROM ${fullTableName}`, [[{ Field: 'foo' }]]); - }); + it('should return true if try to save an old item', async function() { - after(() => { - validQueryManager.cleanQueries(); - }); - - it('should return Error with no model', async function() { + sandbox.stub(QueryBuilder.prototype, 'save').callsFake(() => { + return [{ affectedRows: 2 }]; + }); - await assert.rejects(mysql._prepareFields(), { code: MySQLError.codes.INVALID_MODEL }); + const result = await mysql.save(dummyModel, { + id: 1, + superhero: 'hulk' + }); + assert.equal(result, true); }); - it('should return empty fields with no fields', async function() { - - const preparedFields = await mysql._prepareFields(dummyModel); - assert.deepEqual(preparedFields, expectedPrepareFieldsEmpty); - - }); - it('should return the correct where and placeholders fields', async function() { + it('should return number of rows affected if try to update using filters to match items', async function() { - const preparedFields = await mysql._prepareFields(dummyModel, { foo: 'bar' }); + sandbox.stub(QueryBuilder.prototype, 'update').callsFake(() => { + return 2; + }); - assert.deepEqual(preparedFields, expectedPrepareFields); + const result = await mysql.update(dummyModel, { + superhero: 'Mengano', + filter: { + id: { value: 10, type: 'lesser' } + } + }); + assert.equal(result, 2); }); - it('should return LEFT JOIN query if Join Type field not exist', async function() { - - const fakeFields = { - foo: 'bar', - _joins: [ - { table: 'table_b', alias: 'tb', condition: 'foo' } - ] - }; - - const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); - expectedPrepareFields.joins = ' LEFT JOIN dbname.table_b tb ON foo'; + it('should return 0 if try to update using filters don\'t match any item', async function() { - assert.deepEqual(preparedFields, expectedPrepareFields); + sandbox.stub(QueryBuilder.prototype, 'update').callsFake(() => { + return 0; + }); - expectedPrepareFields.joins = ''; + const result = await mysql.update(dummyModel, { + superhero: 'Mengano', + filter: { + id: { value: 10, type: 'greater' } + } + }); + assert.equal(result, 0); }); - it('should return empty join string if join type is \'\'', async function() { - - const fakeFields = { - foo: 'bar', - _joins: [ - { table: 'table_b', type: '', alias: 'tb', condition: 'foo' } - ] - }; + it('should return 1 if try to multi-Insert a new Item', async function() { - const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); + sandbox.stub(QueryBuilder.prototype, 'save').callsFake(() => { + return [{ affectedRows: 1 }]; + }); - assert.deepEqual(preparedFields, expectedPrepareFields); + const result = await mysql.multiInsert(dummyModel, [{ + id: 3, + superhero: 'hulk' + }]); - expectedPrepareFields.joins = ''; + assert.equal(result, 1); }); - it('should return RIGHT JOIN query with join type is \'RIGHT\' ', async function() { - - const fakeFields = { - foo: 'bar', - _joins: [ - { table: 'table_b', type: 'RIGHT', alias: 'tb', condition: 'foo' } - ] - }; + it('should return the quantity of new items as rows affected if try to multi-Insert new Items', async function() { - const preparedFields = await mysql._prepareFields(dummyModel, fakeFields); - expectedPrepareFields.joins = ' RIGHT JOIN dbname.table_b tb ON foo'; + sandbox.stub(QueryBuilder.prototype, 'save').callsFake(() => { + return [{ affectedRows: 3 }]; + }); - assert.deepEqual(preparedFields, expectedPrepareFields); + const result = await mysql.multiInsert(dummyModel, [ + { id: 4, superhero: 'robin' }, + { id: 5, superhero: 'robocop' }, + { id: 6, superhero: 'moon knight' } + ]); - expectedPrepareFields.joins = ''; + assert.equal(result, 3); }); - }); - - describe('Get Fields', function() { - - it('Should return formatted fields', async function() { - - validQueryManager.addValidQuery(`SHOW COLUMNS FROM ${fullTableName}`, [[{ Field: 'foo', extra: 1 }]]); - const fields = await mysql._getFields(dummyModel); + it('should return the double of quantity of new items as rows affected if try to multi-Insert Items which already exist', async function() { - assert.deepEqual(fields, { - foo: { Field: 'foo', extra: 1 } + sandbox.stub(QueryBuilder.prototype, 'save').callsFake(() => { + return [{ affectedRows: 4 }]; }); - validQueryManager.cleanQueries(); + const result = await mysql.multiInsert(dummyModel, [ + { id: 1, superhero: 'iroman' }, + { id: 2, superhero: 'thor' } + ]); - }); - - it('should return Error with no model', async function() { - - await assert.rejects(mysql._getFields(), { code: MySQLError.codes.INVALID_MODEL }); + assert.equal(result, 4); }); }); - describe('Build field query', function() { - - it('Should build field query correctly for a single value field', function() { - - const values = [['foo', 'test'], ['bar', 1]]; - - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value); - - assert.deepEqual(rows.where, [`${field} = :${field}`]); - assert.deepEqual(rows.placeholders, { [field]: value }); - } + context('when attempting to save/insert/update without a valid knex', function() { + beforeEach(() => { + const knexGetter = {}; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); }); - it('Should build field query correctly for a single value field with alias', function() { - - const values = [['foo', 'test'], ['bar', 1]]; - const alias = 'test'; - - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value, alias); - - assert.deepEqual(rows.where, [`${alias}.${field} = :${field}`]); - assert.deepEqual(rows.placeholders, { [field]: value }); - } - + afterEach(() => { + sandbox.restore(); }); - it('Should build field query correctly for a single value field with added suffix to placeholder', function() { - - const values = [['foo', 'test'], ['bar', 1]]; - - let index = 0; - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); - - const ph = `${field}_${index}`; - assert.deepEqual(rows.where, [`${field} = :${ph}`]); - assert.deepEqual(rows.placeholders, { [ph]: value }); - - index++; - } - + it('should return false if try to insert', async function() { + assert.equal(await mysql.insert(dummyModel, { id: 10, superhero: 'Green Goblin' }), false); }); - it('Should build field query correctly for field with null value', function() { - - const values = [['foo', null], ['bar', null]]; - - let index = 0; - for(const [field, value] of values) { - const rows = MySQL._buildFieldQuery(field, value, '', '_' + index); - - const ph = `${field}_${index}`; - assert.deepEqual(rows.where, [`${field} IS NULL`]); - assert.deepEqual(rows.placeholders, { [ph]: value }); - - index++; - } + it('should return false if try to save', async function() { + assert.equal(await mysql.save(dummyModel, { id: 10, superhero: 'Green Goblin' }), false); }); - it('Should build field query correctly for field with multi values', function() { - - const res = MySQL._buildFieldQuery('foo', [1, '3']); - - assert.deepEqual(res.where, ['foo IN (:foo_0,:foo_1)']); - assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); + it('should return 0 if try to update', async function() { + assert.equal(await mysql.update(dummyModel, { superhero: 'Red Goblin', filters: { id: { value_: 1 } } }), 0); }); - it('Should build field query correctly for field with multi values including NULL', function() { - - const res = MySQL._buildFieldQuery('foo', [1, '3', null]); - assert.deepEqual(res.where, ['(foo IS NULL OR foo IN (:foo_0,:foo_1))']); - assert.deepEqual(res.placeholders, { foo_0: 1, foo_1: '3' }); + it('should return 0 if try to multi-insert', async function() { + assert.equal(await mysql.multiInsert(dummyModel, [{ id: 10, superhero: 'Green Goblin' }, { id: 11, superhero: 'Red Goblin' }]), false); }); }); - }); - - describe('Save Methods', function() { - - let stubFields; - let stubCall; - const insertId = 345; - - before(() => { - - const fields = {}; - ['id', 'foo', 'date_created', 'date_modified'].forEach(field => { fields[field] = field; }); - fields.date_test = { Field: 'date_test', Type: 'datetime' }; - - stubFields = sinon.stub(MySQL.prototype, '_getFields') - .returns(fields); - - stubCall = sinon.stub(MySQL.prototype, '_call') - .returns([{ insertId }]); - }); - - afterEach(() => { - stubCall.resetHistory(); - }); + context('when attempting to save/insert/update without a model', function() { - after(() => { - stubFields.restore(); - stubCall.restore(); - }); - - describe('should save/insert/update', function() { - - it('when attempting to save with valid fields', async function() { - - const result = await mysql.save(dummyModel, { - foo: 'bar', - wrongField: 2 - }); - - const expectedQuery = sanitizeQuery(`INSERT INTO ${fullTableName} - (foo, date_created, date_modified) - VALUES (:foo, :date_created, :date_modified) - ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), foo = VALUES(foo), date_modified = VALUES(date_modified)`); - - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); - - assert.equal(result, insertId); + beforeEach(() => { + const knexGetter = { raw: () => true }; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); }); - it('when attempting to insert with valid fields', async function() { - - const result = await mysql.insert(dummyModel, { - foo: 'bar', - dateCreated: (Date.now() / 1000 | 0), - wrongField: 4 - }); - - const expectedQuery = sanitizeQuery(`INSERT INTO ${fullTableName} - (foo, date_created, date_modified) - VALUES (:foo, :date_created, :date_modified)`); - - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); - - assert.equal(result, insertId); + afterEach(() => { + sandbox.restore(); }); - it('when attempting to update with valid fields', async function() { - - await mysql.update(dummyModel, { - foo: 'bar' - }); - - const expectedQuery = sanitizeQuery(`UPDATE ${fullTableName} - SET foo = :set_foo`); - - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); - assert.deepEqual(stubCall.args[0][1], { set_foo: 'bar' }); + it('should return MySqlError if try to insert', async function() { + await assert.rejects(mysql.insert(null, { id: 10, superhero: 'Green Goblin' }), { code: MySQLError.codes.INVALID_MODEL }); }); - it('when attempting to update with valid fields and filter', async function() { - - await mysql.update(dummyModel, { - _filters: { foo: 'barr' }, - foo: 'bar', - date_test: true - }); - - const expectedQuery = sanitizeQuery(`UPDATE ${fullTableName} - SET foo = :set_foo, date_test = NOW() - WHERE foo = :foo`); + it('should return MySqlError if try to save', async function() { + await assert.rejects(mysql.save(null, { id: 10, superhero: 'Green Goblin' }), { code: MySQLError.codes.INVALID_MODEL }); - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); - assert.deepEqual(stubCall.args[0][1], { set_foo: 'bar', foo: 'barr' }); }); - it('if getFields not return id, date_created and date_modified', async function() { - - const fields2 = { some: 'some' }; - stubFields.returns(fields2); - - const result = await mysql.insert(dummyModel, { - some: 'bar' - }, false); - - const expectedQuery = sanitizeQuery(`INSERT INTO ${fullTableName} - (some) VALUES (:some)`); - - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); - - assert.equal(result, insertId); + it('should return MySqlError if try to update', async function() { + await assert.rejects(mysql.update(null, { superhero: 'Red Goblin', filters: { id: { value_: 1 } } }), + { code: MySQLError.codes.INVALID_MODEL }); }); - }); - - describe('should throw', function() { - - it('when attempting to save with no model', async function() { - await assert.rejects(mysql.save(), { code: MySQLError.codes.INVALID_MODEL }); - - }); - - it('when attempting to update with no model', async function() { - await assert.rejects(mysql.update(), { code: MySQLError.codes.INVALID_MODEL }); - - }); - - it('when attempting to save and fields not found in table structure', async function() { - await assert.rejects(() => mysql.save(dummyModel, { wrongField: 23 }), MySQLError); - }); + it('should return MySqlError if try to multi-insert', async function() { + await assert.rejects(mysql.multiInsert(null, [{ id: 10, superhero: 'Green Goblin' }, { id: 11, superhero: 'Red Goblin' }]), + { code: MySQLError.codes.INVALID_MODEL }); - it('when attempting to update and fields not found in table structure', async function() { - await assert.rejects(() => mysql.update(dummyModel, { wrongField: 23 }), MySQLError); }); - }); + }); describe('Get Methods', function() { - const knexGetter = () => {}; - - let stubKnex; - let stubBuild; + let stubGet; beforeEach(() => { - stubKnex = sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); - stubBuild = sandbox.stub(QueryBuilder.prototype, 'build').callsFake(() => { - return true; - }); + const knexGetter = { raw: () => true }; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); + stubGet = sandbox.stub(QueryBuilder.prototype, 'get'); }); - after(() => { - stubKnex.restore(); - stubBuild.restore(); + afterEach(() => { + sandbox.restore(); + stubGet.restore(); }); const testParams = (params, expectedParams) => { @@ -713,7 +274,7 @@ describe('MySQL module', function() { it('Get Totals in empty Table, should return default values', async function() { - sandbox.stub(MySQL.prototype, 'get').callsFake(() => [{ count: 0 }]); + stubGet.callsFake(() => [{ count: 0 }]); const totalExpected = { page: 1, @@ -729,7 +290,7 @@ describe('MySQL module', function() { const params = {}; - sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => []); + stubGet.callsFake(() => []); const result = await mysql.get(dummyModel, params); @@ -749,7 +310,7 @@ describe('MySQL module', function() { const originalParams = { someFilter: 'foo' }; const params = { ...originalParams }; - const stubResults = sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ result: 1 }, { result: 2 }]); + stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); const result = await mysql.get(dummyModel, params); @@ -757,9 +318,7 @@ describe('MySQL module', function() { testParams(params, originalParams); - stubResults.restore(); - - sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ count: 650 }]); + stubGet.callsFake(() => [{ count: 650 }]); const resultTotals = await mysql.getTotals(dummyModel); @@ -776,7 +335,7 @@ describe('MySQL module', function() { const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; const params = { ...originalParams }; - const stubResults = sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ result: 1 }, { result: 2 }]); + stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); const result = await mysql.get(dummyModel, params); @@ -784,9 +343,7 @@ describe('MySQL module', function() { testParams(params, originalParams); - stubResults.restore(); - - sandbox.stub(QueryBuilder.prototype, 'execute').callsFake(() => [{ count: 650 }]); + stubGet.callsFake(() => [{ count: 650 }]); const resultTotals = await mysql.getTotals(dummyModel); @@ -808,31 +365,20 @@ describe('MySQL module', function() { describe('Remove methods', function() { - let stubFields; - let stubCall; - - before(() => { + let stubRemove; - const fields = {}; - ['id', 'foo', 'date_created', 'date_modified'].forEach(field => { fields[field] = field; }); - - stubFields = sinon.stub(MySQL.prototype, '_getFields') - .returns(fields); - - stubCall = sinon.stub(MySQL.prototype, '_call') - .returns(true); + beforeEach(() => { + const knexGetter = { raw: () => true }; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); + stubRemove = sandbox.stub(QueryBuilder.prototype, 'remove'); }); afterEach(() => { - stubCall.resetHistory(); + sandbox.restore(); + stubRemove.restore(); }); - after(() => { - stubFields.restore(); - stubCall.restore(); - }); - - it('should return Error with no model', async() => { + it('should return Error with no model', async function() { await assert.rejects(mysql.remove(), { code: MySQLError.codes.INVALID_MODEL }); @@ -840,40 +386,30 @@ describe('MySQL module', function() { it('should throw when no filters as object given', async function() { - await assert.rejects(() => mysql.remove(dummyModel), MySQLError); - - await Promise.all( - [1, 'foo', ['foo', 'bar'], false, {}] - .map(async data => assert.rejects(() => mysql.remove(dummyModel, data), MySQLError)) - ); + await assert.rejects(mysql.remove(dummyModel), MySQLError.codes.INVALID_DATA); }); it('should remove when valid fields given', async function() { - await mysql.remove(dummyModel, { - foo: 'bar' - }); + stubRemove.callsFake(() => [{ affectedRows: 1 }]); - const expectedQuery = sanitizeQuery(` - DELETE FROM ${fullTableName} - WHERE foo = :foo`); + const results = await mysql.remove(dummyModel, { + filters: { id: 1 } + }); - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); + assert.equal(results, 1); }); - it('should remove when valids fields given but some invalid too', async() => { - await mysql.remove(dummyModel, { - foo: 'bar', - w: true - }); + it('should return 0 if can not remove', async function() { - const expectedQuery = sanitizeQuery(` - DELETE FROM ${fullTableName} - WHERE foo = :foo`); + stubRemove.rejects(); - assert.equal(sanitizeQuery(stubCall.args[0][0]), expectedQuery); + const results = await mysql.remove(dummyModel, { + filters: { id: 1 } + }); + assert.equal(results, 0); }); }); diff --git a/utils/index.js b/utils/index.js index d868ec4..9132097 100644 --- a/utils/index.js +++ b/utils/index.js @@ -32,14 +32,17 @@ const Utils = { }, /** - * Convert all keys to camelCase from lower_dash - * @param {object} The object to convert + * Convert all keys to camelCase from lower_dash + * @param {object} obj The object to convert * @return {object} The modfied object */ convertKeysToCamelCase(obj) { const result = {}; - for(const [key, value] of Object.entries(obj)) { + if(typeof obj === 'number') + return obj; + + for(const [key, value] of Object.entries({ ...obj })) { const camelCaseKey = Utils.convertToCamelCase(key); From d0d6f75c58f56577d85b0a06301f7f4ba79a65e7 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 21 Jun 2019 10:06:06 -0300 Subject: [PATCH 21/35] Remove Mocks --- mocks/mysql-mock.js | 57 --------------------------------------------- 1 file changed, 57 deletions(-) delete mode 100644 mocks/mysql-mock.js diff --git a/mocks/mysql-mock.js b/mocks/mysql-mock.js deleted file mode 100644 index 6ca7e51..0000000 --- a/mocks/mysql-mock.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -const MySQL = require('mysql2/promise'); - -const queryError = new Error('Invalid Error'); -queryError.errno = 1206; -queryError.code = 'Invalid Query'; - -const validQueryManager = { - addValidQuery(query, resultExpected) { - if(!this._queries) - this._queries = {}; - const count = Object.keys(this._queries).length + 1; - this._queries[count] = { - query, - results: resultExpected - }; - }, - isValidQuery(queryToFind) { - if(!this._queries) - return false; - - const query = Object.values(this._queries).filter(element => element.query === queryToFind); - return !!query.length; - - }, - getValidQueryResult(queryToFind) { - return Object.values(this._queries).filter(element => element.query === queryToFind)[0].results; - }, - cleanQueries() { - if(this._queries) - this._queries = null; - } -}; - -MySQL.createPool = () => { - return ({ - getConnection: async () => ({ - connection: { - config: {} - }, - async query(queryToResolve, placeholders) { - const queryFormated = this.connection.config.queryFormat(queryToResolve, placeholders); - if(!validQueryManager.isValidQuery(queryFormated)) - throw queryError; - - return validQueryManager.getValidQueryResult(queryFormated); - }, - release: () => true - }) - }); -}; - -module.exports = { - MySQL, - validQueryManager -}; From f2e8be279ab82ef828f6a63af69215bedfa25bed Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 21 Jun 2019 10:08:23 -0300 Subject: [PATCH 22/35] Add new Errors handlings --- lib/mysql-error.js | 7 +- lib/mysql.js | 45 +++++--- package-lock.json | 262 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+), 17 deletions(-) diff --git a/lib/mysql-error.js b/lib/mysql-error.js index 3aeba47..6ed4220 100644 --- a/lib/mysql-error.js +++ b/lib/mysql-error.js @@ -13,7 +13,12 @@ class MySQLError extends Error { TOO_MANY_CONNECTION: 6, CONNECTION_ERROR: 7, ALL_POOL_ENDED: 8, - EMPTY_ITEMS: 9 + EMPTY_ITEMS: 9, + INVALID_INSERT: 10, + INVALID_SAVE: 11, + INVALID_UPDATE: 12, + INVALID_MULTI_INSERT: 13, + INVALID_REMOVE: 14 }; } diff --git a/lib/mysql.js b/lib/mysql.js index d0d94df..1f9e92a 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -8,19 +8,33 @@ const QueryBuilder = require('@janiscommerce/query-builder'); const MySQLError = require('./mysql-error'); const Utils = require('../utils'); -const MAX_IDDLE_TIMEOUT = 60 * 5; // In seconds const CONNECTION_LIMIT = 10; - const DEFAULT_LIMIT = 500; +// Knex 0.16.* returns [0] if Insert is OK +const MYSQL_KNEX_INSERT_RESPONSE_OK = 0; + +/* Knex Raw Response with MySql2 module are like: + * [ + * { insertId, affectedRows, info, warnings }, + * undefined + * ] + */ + +const MYSQL_AFFECTED_ROWS_RESPONSE = 'affectedRows'; + class MySQL { static get defaultLimit() { return DEFAULT_LIMIT; } - static get maxIddleTimeout() { - return MAX_IDDLE_TIMEOUT; + static get insertResponse() { + return MYSQL_KNEX_INSERT_RESPONSE_OK; + } + + static get affectedRows() { + return MYSQL_AFFECTED_ROWS_RESPONSE; } /** @@ -52,7 +66,7 @@ class MySQL { wrapIdentifier: (value, origImpl) => origImpl(Utils.convertToSnakeCase(value)), postProcessResponse: result => (Array.isArray(result) ? result.map(Utils.convertKeysToCamelCase) : Utils.convertKeysToCamelCase(result)) }).on('query-error', error => { - logger.error('Query ', error.message); + logger.error('Knex - MySQL ', error.message); }); } @@ -77,11 +91,10 @@ class MySQL { const queryBuilder = new QueryBuilder(this.knex, model); const [result] = await queryBuilder.insert(item); - // if Insert is valid in MySQL return [0] - return result === 0; + return result === this.constructor.insertResponse; } catch(error) { - return false; + throw new MySQLError(error.message, MySQLError.codes.INVALID_INSERT); } } @@ -104,10 +117,10 @@ class MySQL { const queryBuilder = new QueryBuilder(this.knex, model); const [result] = await queryBuilder.save(item); - return !!result.affectedRows; + return !!result[this.constructor.affectedRows]; } catch(error) { - return false; + throw new MySQLError(error.message, MySQLError.codes.INVALID_SAVE); } } @@ -132,7 +145,7 @@ class MySQL { return queryBuilder.update(params, filters); } catch(error) { - return 0; + throw new MySQLError(error.message, MySQLError.codes.INVALID_UPDATE); } } @@ -170,7 +183,7 @@ class MySQL { return result; } catch(error) { - return []; + throw new MySQLError(error.message, MySQLError.codes.INVALID_GET); } } @@ -225,10 +238,10 @@ class MySQL { const queryBuilder = new QueryBuilder(this.knex, model); const [result] = await queryBuilder.save(items); - return result.affectedRows; + return result[this.constructor.affectedRows]; } catch(error) { - return 0; + throw new MySQLError(error.message, MySQLError.codes.INVALID_MULTI_INSERT); } } @@ -256,10 +269,10 @@ class MySQL { const queryBuilder = new QueryBuilder(this.knex, model); const [result] = await queryBuilder.remove(filters, joins); - return result.affectedRows; + return result[this.constructor.affectedRows]; } catch(error) { - return 0; + throw new MySQLError(error.message, MySQLError.codes.INVALID_REMOVE); } } diff --git a/package-lock.json b/package-lock.json index 60361eb..835464b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -177,6 +177,12 @@ "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.26.tgz", "integrity": "sha512-aj2mrBLn5ky0GmAg6IPXrQjnN0iB/ulozuJ+oZdrHRAzRbXjGmu4UXsNCjFvPbSaaPZmniocdOzsM392qLOlmQ==" }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, "acorn": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", @@ -453,6 +459,32 @@ "write-file-atomic": "^2.4.2" } }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -491,6 +523,12 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -681,6 +719,46 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "cp-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", @@ -1613,6 +1691,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -1785,6 +1869,136 @@ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, + "husky": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-2.4.1.tgz", + "integrity": "sha512-ZRwMWHr7QruR22dQ5l3rEGXQ7rAQYsJYqaeCd+NyOsIFczAtqaApZQP3P4HwLZjCtFbm3SUNYoKuoBXX3AYYfw==", + "dev": true, + "requires": { + "cosmiconfig": "^5.2.0", + "execa": "^1.0.0", + "find-up": "^3.0.0", + "get-stdin": "^7.0.0", + "is-ci": "^2.0.0", + "pkg-dir": "^4.1.0", + "please-upgrade-node": "^3.1.1", + "read-pkg": "^5.1.1", + "run-node": "^1.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, + "read-pkg": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.1.1.tgz", + "integrity": "sha512-dFcTLQi6BZ+aFUaICg7er+/usEoqFdQxiEBsEMNGoipenihtxxtdrQuBXvyANCEI8VuUIVYFgeHGx9sLLvim4w==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^4.0.0", + "type-fest": "^0.4.1" + } + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1928,6 +2142,15 @@ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -1969,6 +2192,12 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -3209,6 +3438,15 @@ "find-up": "^2.1.0" } }, + "please-upgrade-node": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", + "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -3421,6 +3659,12 @@ "is-promise": "^2.1.0" } }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, "rxjs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz", @@ -3454,6 +3698,12 @@ "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", @@ -3537,6 +3787,12 @@ "supports-color": "^5.5.0" } }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -4102,6 +4358,12 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "type-fest": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", + "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", + "dev": true + }, "uglify-js": { "version": "3.5.11", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.11.tgz", From 8906cf0cbbb84a235cbf8cab84feba416a694036 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 21 Jun 2019 10:08:55 -0300 Subject: [PATCH 23/35] Refactor Tests --- tests/mysql-test.js | 219 ++++++++++++++++++++++++++++---------------- tests/utils-test.js | 45 +++++---- 2 files changed, 168 insertions(+), 96 deletions(-) diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 7a9583b..bcb316a 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -66,16 +66,16 @@ describe('MySQL module', function() { assert.equal(result, true); }); - it('should return false if try to insert an item that already exist', async function() { + it('should throw MySqlError if try to insert an item that already exist', async function() { sandbox.stub(QueryBuilder.prototype, 'insert').rejects(); - const result = await mysql.insert(dummyModel, { + const item = { id: 1, superhero: 'superman' - }); + }; - assert.equal(result, false); + await assert.rejects(mysql.insert(dummyModel, item), { code: MySQLError.codes.INVALID_INSERT }); }); it('should return true if try to save a new item', async function() { @@ -199,21 +199,33 @@ describe('MySQL module', function() { }); it('should return false if try to insert', async function() { - assert.equal(await mysql.insert(dummyModel, { id: 10, superhero: 'Green Goblin' }), false); + await assert.rejects(mysql.insert(dummyModel, { id: 10, superhero: 'Green Goblin' }), { code: MySQLError.codes.INVALID_INSERT }); }); it('should return false if try to save', async function() { - assert.equal(await mysql.save(dummyModel, { id: 10, superhero: 'Green Goblin' }), false); + await assert.rejects(mysql.save(dummyModel, { id: 10, superhero: 'Green Goblin' }), { code: MySQLError.codes.INVALID_SAVE }); }); it('should return 0 if try to update', async function() { - assert.equal(await mysql.update(dummyModel, { superhero: 'Red Goblin', filters: { id: { value_: 1 } } }), 0); + const item = { + superhero: 'Red Goblin', + filters: { + id: { value_: 1 } + } + }; + + await assert.rejects(mysql.update(dummyModel, item), { code: MySQLError.codes.INVALID_UPDATE }); }); it('should return 0 if try to multi-insert', async function() { - assert.equal(await mysql.multiInsert(dummyModel, [{ id: 10, superhero: 'Green Goblin' }, { id: 11, superhero: 'Red Goblin' }]), false); + const items = [ + { id: 10, superhero: 'Green Goblin' }, + { id: 11, superhero: 'Red Goblin' } + ]; + + await assert.rejects(mysql.multiInsert(dummyModel, items), { code: MySQLError.codes.INVALID_MULTI_INSERT }); }); }); @@ -251,116 +263,164 @@ describe('MySQL module', function() { }); }); - }); + context('when attempting to save/insert/update with no items or values', function() { - describe('Get Methods', function() { + beforeEach(() => { + const knexGetter = { raw: () => true }; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); + }); - let stubGet; + afterEach(() => { + sandbox.restore(); + }); - beforeEach(() => { - const knexGetter = { raw: () => true }; - sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); - stubGet = sandbox.stub(QueryBuilder.prototype, 'get'); - }); + it('should return MySqlError if try to insert', async function() { + await assert.rejects(mysql.insert(dummyModel), { code: MySQLError.codes.EMPTY_FIELDS }); + }); - afterEach(() => { - sandbox.restore(); - stubGet.restore(); + it('should return MySqlError if try to save', async function() { + await assert.rejects(mysql.save(dummyModel), { code: MySQLError.codes.EMPTY_FIELDS }); + + }); + + it('should return MySqlError if try to update', async function() { + await assert.rejects(mysql.update(dummyModel), { code: MySQLError.codes.EMPTY_FIELDS }); + + }); + + it('should return MySqlError if try to multi-insert', async function() { + await assert.rejects(mysql.multiInsert(dummyModel,), { code: MySQLError.codes.EMPTY_FIELDS }); + + }); }); - const testParams = (params, expectedParams) => { - assert.deepEqual(params, expectedParams, 'shouldn\'t modify ofiginal params'); - }; + }); - it('Get Totals in empty Table, should return default values', async function() { + describe('Get Methods', function() { - stubGet.callsFake(() => [{ count: 0 }]); + context('when try to get items with valid configuration', function() { - const totalExpected = { - page: 1, - pageSize: 500, - pages: 1, - total: 0 + let stubGet; + + beforeEach(() => { + const knexGetter = { raw: () => true }; + sandbox.stub(MySQL.prototype, 'knex').get(() => knexGetter); + stubGet = sandbox.stub(QueryBuilder.prototype, 'get'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const testParams = (params, expectedParams) => { + assert.deepEqual(params, expectedParams, 'shouldn\'t modify ofiginal params'); }; - assert.deepEqual(await mysql.getTotals(dummyModel), totalExpected); - }); + it('should return default values getting totals with empty tables', async function() { - it('Should return empty results and totals with zero values', async function() { + stubGet.callsFake(() => [{ count: 0 }]); - const params = {}; + const totalExpected = { + page: 1, + pageSize: 500, + pages: 1, + total: 0 + }; - stubGet.callsFake(() => []); + assert.deepEqual(await mysql.getTotals(dummyModel), totalExpected); + }); - const result = await mysql.get(dummyModel, params); + it('Should return empty results and totals with zero values', async function() { - assert.deepEqual(result, []); + const params = {}; - const resultTotals = await mysql.getTotals(dummyModel); + stubGet.callsFake(() => []); - assert.deepEqual(resultTotals, { total: 0, pages: 0 }); + const result = await mysql.get(dummyModel, params); - testParams(params, {}); + assert.deepEqual(result, []); + const resultTotals = await mysql.getTotals(dummyModel); - }); + assert.deepEqual(resultTotals, { total: 0, pages: 0 }); - it('Should return results and totals only with filters', async function() { + testParams(params, {}); + }); + + it('Should return results and totals only with filters', async function() { - const originalParams = { someFilter: 'foo' }; - const params = { ...originalParams }; + const originalParams = { someFilter: 'foo' }; + const params = { ...originalParams }; - stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); + stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); - const result = await mysql.get(dummyModel, params); + const result = await mysql.get(dummyModel, params); - assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); + assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); - testParams(params, originalParams); + testParams(params, originalParams); - stubGet.callsFake(() => [{ count: 650 }]); + stubGet.callsFake(() => [{ count: 650 }]); - const resultTotals = await mysql.getTotals(dummyModel); + const resultTotals = await mysql.getTotals(dummyModel); - assert.deepEqual(resultTotals, { - total: 650, - page: 1, - pageSize: 500, - pages: 2 + assert.deepEqual(resultTotals, { + total: 650, + page: 1, + pageSize: 500, + pages: 2 + }); }); - }); - it('Should return results and totals', async function() { + it('Should return results and totals', async function() { - const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; - const params = { ...originalParams }; + const originalParams = { someFilter: 'foo', page: 4, limit: 10 }; + const params = { ...originalParams }; - stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); + stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); - const result = await mysql.get(dummyModel, params); + const result = await mysql.get(dummyModel, params); - assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); + assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); - testParams(params, originalParams); + testParams(params, originalParams); - stubGet.callsFake(() => [{ count: 650 }]); + stubGet.callsFake(() => [{ count: 650 }]); - const resultTotals = await mysql.getTotals(dummyModel); + const resultTotals = await mysql.getTotals(dummyModel); - assert.deepEqual(resultTotals, { - total: 650, - page: 4, - pageSize: 10, - pages: 65 + assert.deepEqual(resultTotals, { + total: 650, + page: 4, + pageSize: 10, + pages: 65 + }); }); + }); - it('Should throws Error when try to get with no model', async function() { - await assert.rejects(mysql.get(), { code: MySQLError.codes.INVALID_MODEL }); + context('when try to get items with invalid configuration', function() { - await assert.rejects(mysql.getTotals(), { code: MySQLError.codes.INVALID_MODEL }); + afterEach(() => { + sandbox.restore(); + }); + + it('should throw MySqlError if knex is invalid', async function() { + + sandbox.stub(MySQL.prototype, 'knex').get(() => {}); + await assert.rejects(mysql.get(dummyModel, {}), { code: MySQLError.codes.INVALID_GET }); + + }); + + it('should throws MySqlError when try to get with no model', async function() { + + await assert.rejects(mysql.get(), { code: MySQLError.codes.INVALID_MODEL }); + await assert.rejects(mysql.getTotals(), { code: MySQLError.codes.INVALID_MODEL }); + + }); }); + }); describe('Remove methods', function() { @@ -375,18 +435,17 @@ describe('MySQL module', function() { afterEach(() => { sandbox.restore(); - stubRemove.restore(); }); - it('should return Error with no model', async function() { + it('should throw MySqlError with no model', async function() { await assert.rejects(mysql.remove(), { code: MySQLError.codes.INVALID_MODEL }); }); - it('should throw when no filters as object given', async function() { + it('should throw MySqlError when no filters as object given', async function() { - await assert.rejects(mysql.remove(dummyModel), MySQLError.codes.INVALID_DATA); + await assert.rejects(mysql.remove(dummyModel), { code: MySQLError.codes.INVALID_DATA }); }); @@ -401,15 +460,15 @@ describe('MySQL module', function() { assert.equal(results, 1); }); - it('should return 0 if can not remove', async function() { + it('should throw MySqlError if can not remove', async function() { stubRemove.rejects(); - const results = await mysql.remove(dummyModel, { + const params = { filters: { id: 1 } - }); + }; - assert.equal(results, 0); + await assert.rejects(mysql.remove(dummyModel, params), { code: MySQLError.codes.INVALID_REMOVE }); }); }); diff --git a/tests/utils-test.js b/tests/utils-test.js index 57b2365..e4b2c40 100644 --- a/tests/utils-test.js +++ b/tests/utils-test.js @@ -4,31 +4,44 @@ const assert = require('assert'); const Utils = require('./../utils'); -/** Setup **/ +/** Setup * */ /* eslint-disable prefer-arrow-callback */ describe('Utils', function() { - describe('Should convert keys to CamelCase', function() { - const data = { - order_form_id: 'yes', - check_boolean: true, - check_int: 5, - status: true - }; + describe('convertKeysToCamelCase', function() { - const result = Utils.convertKeysToCamelCase(data); + it('Should convert objects keys to CamelCase', function() { - assert.equal(result.orderFormId, 'yes'); - assert.equal(result.checkBoolean, true); - assert.equal(result.checkInt, 5); + const data = { + order_form_id: 'yes', + check_boolean: true, + check_int: 5, + status: true + }; - assert.equal(result.status, true); + const result = Utils.convertKeysToCamelCase(data); - assert.equal(result.order_form_id, undefined); - assert.equal(result.check_boolean, undefined); - assert.equal(result.check_int, undefined); + assert.equal(result.orderFormId, 'yes'); + assert.equal(result.checkBoolean, true); + assert.equal(result.checkInt, 5); + + assert.equal(result.status, true); + + assert.equal(result.order_form_id, undefined); + assert.equal(result.check_boolean, undefined); + assert.equal(result.check_int, undefined); + + }); + + it('Should no-convert if there is a number instead of object', function() { + const data = 11; + + const result = Utils.convertKeysToCamelCase(data); + + assert.equal(result, data); + }); }); describe('Should convert string to snake_case', function() { From 381a42fea72a7bb70821fc88c2288a8c44e3f113 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 21 Jun 2019 15:11:51 -0300 Subject: [PATCH 24/35] Upgrade README --- README.md | 358 +++++++++++++++++++++++++++++++++++++++++---- lib/mysql-error.js | 21 +-- lib/mysql.js | 82 ++++++----- package.json | 5 +- 4 files changed, 382 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index c438b17..6969502 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,362 @@ -# MySql +# mysql [![Build Status](https://travis-ci.org/janis-commerce/mysql.svg?branch=JCN-49-janis-mysql)](https://travis-ci.org/janis-commerce/mysql) [![Coverage Status](https://coveralls.io/repos/github/janis-commerce/mysql/badge.svg?branch=JCN-49-janis-mysql)](https://coveralls.io/github/janis-commerce/mysql?branch=JCN-49-janis-mysql) +A Driver for **MySQL** Database. + +- - - + ## Installation ``` npm install @janiscommerce/mysql ``` +- - - + +## Configuration + +The **TABLES** must be created before start using this driver. + +This driver use a configuration `object` with the database config data, it look like these : + +```javascript + +const config = { + host: 'someHost', // host name where the database is connected + user: 'yourUser', // username + password: 'yourPassword', // password + database: 'your_database_name', // the database name, could not exist + port: 3006, // the port where the database is connected + connectionLimit: 5000, // A connection limit, 5000 by default + prefix: 'yourPrefix' // if you use some prefix, could not exist +} + +``` +- - - ## API -* `new MySQL(config)` +* `new MySQL(config)`, MySQL constructor, to start using it. + + - `config`: *type* `OBJECT`, the database configuration. + +* `insert(model, item)` **ASYNCHRONOUS**, Insert an individual object in the database. + + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - `item`: *type* `OBJECT`, the object to be inserted. + - **Returns**, `boolean` if the object was inserted correctly returns `true`. - MySQL driver Module. `config` object with the database configuration. +* `save(model, item)` **ASYNCHRONOUS**, Saved an individual object in the database. Duplicate Objects updates it. -* `save(model, item)` + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - `item`: *type* `OBJECT`,the object to be saved. + - **Returns**, `Promise` with `true` if the object was saved correctly. - Saved an item in the database. Returns the Row affected - - `model` a Model class, used to setup correctly the insertion. - - `item` the object to be saved. +* `multiInsert(model, items)` **ASYNCHRONOUS**, Performs an Insert of multiple objects. Duplicate Objects updates it. -* `get(model, parametres)` + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - `items`: *type* `ARRAY`, the list of objects to be saved. + - **Returns**, `Promise` with `number` of the quantity of rows were updated correctly. - Search in the database and return a `Promise` with the results. - - `model` a Model class, used to get the correct search. - - `parametres` it's an `object` with the field and options to make the query. +* `update(model, parametres)` **ASYNCHRONOUS**, Update rows. -* `insert(model, values, allowUpserted)` + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - `parametres`: *type* `OBJECT`, with the following `keys` to make the changes: + * `fields`: *type* `object`, *key*: field to change, *value*: new value. + * `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). + - **Returns**, `number` of the quantity of rows were updated correctly. - Insert or Update a Row. Returns the Row affected -* `update(model, item)` +* `get(model, parametres)` **ASYNCHRONOUS**, Search rows in the database. - Update a row. + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - `parametres`: *type* `OBJECT`, with the following `keys` to make the query: + * `fields`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Fields.md). + * `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). + * `joins`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Joins.md). + * `order`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Orders.md). + * `group`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Groups.md). + * `limit`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Pagination.md). + * *special functions*: Learn [More](https://github.com/janis-commerce/query-builder/docs/Special-functions.md). + - **Returns**, `Array` of `objects` of *rows* founds. -* `multiInsert(model, items)` - Performs a multi insert. +* `remove(model, parametres)` **ASYNCHRONOUS**, Remove rows in the database. -* `remove()` + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - `parametres`: *type* `OBJECT`, with the following `keys` to make the changes: + - `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). + - `joins`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Joins.md). + - **Returns**, `number` of the quantity of rows were removed correctly. -## Config -The configuration object looks like: +- - - + +## Errors + +The errors are informed with a `MySQLError` with the proper message for each error. + +The codes are the following: + +|Code |Description | +|-------|---------------------------| +|1 |Invalid Model | +|2 |Invalid Insert | +|3 |Invalid Save | +|4 |Invalid Update | +|5 |Invalid Multi-Insert | +|6 |Invalid Remove | +|7 |Empty Fields | + +- - - + +## Usage ```javascript +const Mysql = require('@janniscomercer/mysql'); const config = { - host: 'someHost', // host name where the database is connected - user: 'yourUser', // username - password: 'yourPassword', // password - database: 'your_database_name', // the database name, could not exist - port: 3006, // the port where the database is connected - connectionLimit: 5000, // A connection limit, 5000 by default - prefix: 'yourPrefix' // if you use some prefix, could not exist + host: 'localhost', + user: 'root', + password: '20192106', + database: 'fizzmod', + port: 3006, +}; + +// Initialize +// Table Already Created + +const mysql = new Mysql(config); + +// Some Model with the right setup to use +// fields = id (primary key), name , genre, calification +const movieModel = new movieModel(); + +let movieResponse; +let movieItem; +let movieItems; + +// INSERT a new Item + +movieItem = { + id: 1, + name: 'Titanic', + genre: 'Drama', + calification: 2, + nation: 'EEUU' // this field will not be inserted +}; + +try { + movieResponse = await mysql.insert(movieModel, movieItem); + // Response: TRUE + console.log('Movie Saved'); // Print in Console +} catch (error) { + console.log('These Movie can\'t be saved.'); } -``` +// INSERT an Item that already exist -- - - +movieItem = { + id: 1, + name: 'Titanic', + genre: 'Drama', + calification: 10 +}; + +try { + // will throw error + movieResponse = await mysql.insert(movieModel, movieItem); + console.log('Movie Saved'); + +} catch(error) { + + console.log('This Movie can\'t be saved.'); // Print in Console +} + +// SAVE an Item that already exist + +movieItem = { + id: 1, + name: 'Titanic', + genre: 'Drama', + calification: 1 +}; + +try { + + movieResponse = await mysql.save(movieModel, movieItem); + // Update and Response: TRUE + console.log('Movie Saved'); // Print in Console + +} catch(error) { + + console.log('This Movie can\'t be saved.'); +} + +// SAVE an new Item + +movieItem = { + id: 2, + name: 'Lord of the Rings 1', + genre: 'Fantasy', + calification: 1 +}; + +try { + + movieResponse = await mysql.save(movieModel, movieItem); + // Insert and Response: TRUE + console.log('Movie Saved'); // Print in Console + +} catch(error) { + + console.log('This Movie can\'t be saved.'); +} + +// MULTI INSERT multiple Items + +movieItems = [ + { + id: 3, + name: 'Lord of the Rings 2', + genre: 'Fantasy', + calification: 8 + }, + { + id: 4, + name: 'Avengers 3', + genre: 'Action', + calification: 9 + }, + { + id: 5, + name: 'Lord of the Rings 3', + genre: 'Fantasy', + calification: 9 + }, + { + id: 6, + name: 'Scream', + genre: 'Terror', + calification: 6 + }, + { + id: 2, + name: 'Lord of the Rings 1', + genre: 'Fantasy', + calification: 7 + }, + { + id: 7, + name: 'Sharkanado', + genre: 'Comedy', + calification: 3 + } +]; + +try { + + movieResponse = await mysql.multiInsert(movieModel, movieItem); + // insert 4 movies and update 1, + // Response: 5, + console.log('Movies Saved', movieResponse); // Print in Console + +} catch(error) { + + console.log('These Movie can\'t be saved.'); +} + +// MULTI INSERT multiple Items + +let params = { + fields: { + calification: 9 + }, + filters: { + genre: 'Fantasy' + } +}; + +try { + + movieResponse = await mysql.update(movieModel, params); + // Response: 3 + console.log('Movies Updated', movieResponse); // Print in Console + +} catch(error) { + + console.log('These Movie can\'t be update.'); +} + +// GET + +params = {}; + +try { + + movieResponse = await mysql.get(movieModel, params); + // Response: Array with All movies and his fields + /* + [ + { id: 1, name: 'Titanic', genre: 'Drama', calfication: 1, date_created: 1239218, date_modified: 1239918 }, + { id: 2, name: 'Lord of the Rings 1', genre: 'Fantasy', calfication: 9, date_created: 1240000, date_modified: 1242000 }, + ... + { id: 4, name: 'Avengers 3', genre: 'Action', calfication: 1, date_created: 1241000, date_modified: 1241000 }, + ... + ] + */ + console.log('Movies ', movieResponse); // Print in Console + +} catch(error) { + + console.log('These Movie can\'t be get.'); +} + +// GET only fields required and with filters + +params = { + fields: ['name'], + filters: { + genre: 'Fantasy' + } +}; + +try { + + movieResponse = await mysql.get(movieModel, params); + // Response: Array with All movies with fields required and passed the filter + /* + [ + { id: 2, name: 'Lord of the Rings 1', genre: 'Fantasy', calfication: 9, date_created: 1240000, date_modified: 1242000 }, + { id: 3, name: 'Lord of the Rings 2', genre: 'Fantasy', calfication: 9, date_created: 1241000, date_modified: 1242000 }, + { id: 5, name: 'Lord of the Rings 3', genre: 'Fantasy', calfication: 9, date_created: 1241000, date_modified: 1242000 } + ] + */ + console.log('Movies ', movieResponse); // Print in Console + +} catch(error) { + + console.log('These Movie can\'t be get.'); +} + +// REMOVE items + +params = { + filters: { + genre: 'Terror' + } +}; + +try { + + movieResponse = await mysql.remove(movieModel, params); + // Response: 1 + console.log('Movies ', movieResponse); // Print in Console + +} catch(error) { + + console.log('These Movie can\'t be removed.'); +} -## Usage \ No newline at end of file +``` \ No newline at end of file diff --git a/lib/mysql-error.js b/lib/mysql-error.js index 6ed4220..789628d 100644 --- a/lib/mysql-error.js +++ b/lib/mysql-error.js @@ -5,20 +5,13 @@ class MySQLError extends Error { static get codes() { return { - EMPTY_FIELDS: 1, - INVALID_STATEMENT: 2, - INVALID_MODEL: 3, - INVALID_DATA: 4, - INVALID_QUERY: 5, - TOO_MANY_CONNECTION: 6, - CONNECTION_ERROR: 7, - ALL_POOL_ENDED: 8, - EMPTY_ITEMS: 9, - INVALID_INSERT: 10, - INVALID_SAVE: 11, - INVALID_UPDATE: 12, - INVALID_MULTI_INSERT: 13, - INVALID_REMOVE: 14 + INVALID_MODEL: 1, + INVALID_INSERT: 2, + INVALID_SAVE: 3, + INVALID_UPDATE: 4, + INVALID_MULTI_INSERT: 5, + INVALID_REMOVE: 6, + EMPTY_FIELDS: 7 }; } diff --git a/lib/mysql.js b/lib/mysql.js index 1f9e92a..c5c4f4d 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -11,12 +11,20 @@ const Utils = require('../utils'); const CONNECTION_LIMIT = 10; const DEFAULT_LIMIT = 500; -// Knex 0.16.* returns [0] if Insert is OK +// QueryBuilder uses Knex 0.16.* returns [0] if Insert is OK const MYSQL_KNEX_INSERT_RESPONSE_OK = 0; -/* Knex Raw Response with MySql2 module are like: +/* QueryBuilder uses Knex Raw in Save and Removed methods + * Knex Raw Response with MySql2 module look like this: * [ - * { insertId, affectedRows, info, warnings }, + * { + * fieldCount: [number], + * insertId: [number], // if rows was updated show the id, if rows was inserted show 0 + * affectedRows: [number], // (number of rows inserted/removed) + (number of rows updated) * 2 + * info: [string], // example: 'Records: 1, Duplicates: 1, Warning: 0' + * serverStatus: [number], // normally is 2 + * warningStatus: [number] + * }, * undefined * ] */ @@ -75,7 +83,7 @@ class MySQL { /** * Insert an item into the database - * @param {Class} model - Model Class + * @param {instance} model - Model instance * @param {object} item - The item to insert * @returns {Promise} True if success */ @@ -100,8 +108,8 @@ class MySQL { } /** - * Save a new item in the SQL database - * @param {Class} model Model Class + * Save a new items or update it in the SQL database. + * @param {instance} model Model Class Instance * @param {object} item object to saved * @returns {Promise} True if success */ @@ -124,6 +132,32 @@ class MySQL { } } + /** + * Perform a multi insert + * @param {instance} model Model instance + * @param {array} items - The items to insert + * @returns {Promise} number of affected rows + */ + async multiInsert(model, items) { + + if(!model) + throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); + + if(!items || !items.length) + throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS); + + try { + const queryBuilder = new QueryBuilder(this.knex, model); + const [result] = await queryBuilder.save(items); + + return result[this.constructor.affectedRows]; + + } catch(error) { + throw new MySQLError(error.message, MySQLError.codes.INVALID_MULTI_INSERT); + } + } + + /** * Update a row * @param {object} params - The value to change and the filters @@ -137,12 +171,13 @@ class MySQL { if(!params || !Object.keys(params).length) throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); - const { filters } = params; - delete params.filters; + const { fields, filters } = params; try { const queryBuilder = new QueryBuilder(this.knex, model); - return queryBuilder.update(params, filters); + const result = await queryBuilder.update(fields, filters); + // QueryBuilder/Knex response are the number of rows updated + return result; } catch(error) { throw new MySQLError(error.message, MySQLError.codes.INVALID_UPDATE); @@ -219,32 +254,6 @@ class MySQL { }; } - /** - * Perform a multi insert - * @param {class} model Model Class - * @param {array} items - The items to insert - * @returns {Promise} number of affected rows - */ - /* istanbul ignore next */ - async multiInsert(model, items) { - - if(!model) - throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - - if(!items || !items.length) - throw new MySQLError('Items are required', MySQLError.codes.EMPTY_FIELDS); - - try { - const queryBuilder = new QueryBuilder(this.knex, model); - const [result] = await queryBuilder.save(items); - - return result[this.constructor.affectedRows]; - - } catch(error) { - throw new MySQLError(error.message, MySQLError.codes.INVALID_MULTI_INSERT); - } - } - /** * Remove by fields and value * @@ -262,9 +271,6 @@ class MySQL { const { filters, joins } = params; - delete params.filters; - delete params.joins; - try { const queryBuilder = new QueryBuilder(this.knex, model); const [result] = await queryBuilder.remove(filters, joins); diff --git a/package.json b/package.json index b99a52e..ab492cc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@janiscommerce/mysql", "version": "1.0.0", - "description": "", + "description": "Driver for MySQL Databse", "main": "index.js", "scripts": { "lint": "eslint index.js lib/ tests/", @@ -37,6 +37,7 @@ "husky": "^2.4.1" }, "files": [ - "lib/" + "lib/", + "utils" ] } From 5bebc9de14113e0057f541b65a3310f01cf260e3 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Mon, 24 Jun 2019 14:34:48 -0300 Subject: [PATCH 25/35] Changed update params to be similar to MongoDB driver --- lib/mysql.js | 22 +++++++--------------- tests/mysql-test.js | 39 ++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/lib/mysql.js b/lib/mysql.js index c5c4f4d..688ff0a 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -11,9 +11,6 @@ const Utils = require('../utils'); const CONNECTION_LIMIT = 10; const DEFAULT_LIMIT = 500; -// QueryBuilder uses Knex 0.16.* returns [0] if Insert is OK -const MYSQL_KNEX_INSERT_RESPONSE_OK = 0; - /* QueryBuilder uses Knex Raw in Save and Removed methods * Knex Raw Response with MySql2 module look like this: * [ @@ -37,10 +34,6 @@ class MySQL { return DEFAULT_LIMIT; } - static get insertResponse() { - return MYSQL_KNEX_INSERT_RESPONSE_OK; - } - static get affectedRows() { return MYSQL_AFFECTED_ROWS_RESPONSE; } @@ -98,8 +91,8 @@ class MySQL { try { const queryBuilder = new QueryBuilder(this.knex, model); - const [result] = await queryBuilder.insert(item); - return result === this.constructor.insertResponse; + const result = await queryBuilder.insert(item); + return !!result; } catch(error) { throw new MySQLError(error.message, MySQLError.codes.INVALID_INSERT); @@ -160,22 +153,21 @@ class MySQL { /** * Update a row - * @param {object} params - The value to change and the filters + * @param {object} values - The value to change + * @param {object} filters - The filters * @returns {Promise} number of rows updated */ - async update(model, params) { + async update(model, values, filters) { if(!model) throw new MySQLError('Invalid or Empty Model', MySQLError.codes.INVALID_MODEL); - if(!params || !Object.keys(params).length) + if(!values || !Object.keys(values).length) throw new MySQLError('Update must have fields', MySQLError.codes.EMPTY_FIELDS); - const { fields, filters } = params; - try { const queryBuilder = new QueryBuilder(this.knex, model); - const result = await queryBuilder.update(fields, filters); + const result = await queryBuilder.update(values, filters); // QueryBuilder/Knex response are the number of rows updated return result; diff --git a/tests/mysql-test.js b/tests/mysql-test.js index bcb316a..0d1c38f 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -109,32 +109,32 @@ describe('MySQL module', function() { it('should return number of rows affected if try to update using filters to match items', async function() { + const fields = { superhero: 'Mengano' }; + const filters = { + id: { value: 10, type: 'lesser' } + }; + sandbox.stub(QueryBuilder.prototype, 'update').callsFake(() => { return 2; }); - const result = await mysql.update(dummyModel, { - superhero: 'Mengano', - filter: { - id: { value: 10, type: 'lesser' } - } - }); + const result = await mysql.update(dummyModel, fields, filters); assert.equal(result, 2); }); it('should return 0 if try to update using filters don\'t match any item', async function() { + const fields = { superhero: 'Mengano' }; + const filters = { + id: { value: 10, type: 'greater' } + }; + sandbox.stub(QueryBuilder.prototype, 'update').callsFake(() => { return 0; }); - const result = await mysql.update(dummyModel, { - superhero: 'Mengano', - filter: { - id: { value: 10, type: 'greater' } - } - }); + const result = await mysql.update(dummyModel, fields, filters); assert.equal(result, 0); }); @@ -208,14 +208,12 @@ describe('MySQL module', function() { }); it('should return 0 if try to update', async function() { - const item = { - superhero: 'Red Goblin', - filters: { - id: { value_: 1 } - } + const fields = { superhero: 'Red Goblin' }; + const filters = { + id: { value_: 1 } }; - await assert.rejects(mysql.update(dummyModel, item), { code: MySQLError.codes.INVALID_UPDATE }); + await assert.rejects(mysql.update(dummyModel, fields, filters), { code: MySQLError.codes.INVALID_UPDATE }); }); @@ -251,7 +249,10 @@ describe('MySQL module', function() { }); it('should return MySqlError if try to update', async function() { - await assert.rejects(mysql.update(null, { superhero: 'Red Goblin', filters: { id: { value_: 1 } } }), + const fields = { superhero: 'Red Goblin' }; + const filters = { id: { value_: 1 } }; + + await assert.rejects(mysql.update(null, fields, filters), { code: MySQLError.codes.INVALID_MODEL }); }); From 900a84c3159a7d7109eab275a20ea656f1c6b2ca Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Tue, 25 Jun 2019 09:53:05 -0300 Subject: [PATCH 26/35] Updated Readme --- README.md | 7 +++---- lib/mysql.js | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6969502..d9213b6 100644 --- a/README.md +++ b/README.md @@ -59,12 +59,11 @@ const config = { - `items`: *type* `ARRAY`, the list of objects to be saved. - **Returns**, `Promise` with `number` of the quantity of rows were updated correctly. -* `update(model, parametres)` **ASYNCHRONOUS**, Update rows. +* `update(model, values, filters)` **ASYNCHRONOUS**, Update rows. - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. - - `parametres`: *type* `OBJECT`, with the following `keys` to make the changes: - * `fields`: *type* `object`, *key*: field to change, *value*: new value. - * `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). + - `values`: *type* `object`, *key*: field to change, *value*: new value. + - `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). - **Returns**, `number` of the quantity of rows were updated correctly. diff --git a/lib/mysql.js b/lib/mysql.js index 688ff0a..fb7461a 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -91,6 +91,8 @@ class MySQL { try { const queryBuilder = new QueryBuilder(this.knex, model); + // Insert could response with an Array with 0 value if Primary Key is not AutoIncremental + // or Primary Key value if is ID and Integer and AutoIncremental const result = await queryBuilder.insert(item); return !!result; From c18be7833361b930ca553043a7fac763c8035e00 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 11 Jul 2019 14:42:04 -0300 Subject: [PATCH 27/35] Fixed show wrong page number --- lib/mysql.js | 14 ++++++++++++-- tests/mysql-test.js | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/mysql.js b/lib/mysql.js index fb7461a..41009a6 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -240,11 +240,21 @@ class MySQL { limit: 1 }); + const pages = params.limit ? Math.ceil(result.count / params.limit) : 1; + let page; + + // Control that page are not bigger than total pages + // -----------------> HACER TEST DE ESTO PARA EL COVERAGE <------------ + if(!params.page) + page = 1; + else + page = pages < params.page ? pages : params.page; + return { total: result.count, - page: params.page ? params.page : 1, + page, pageSize: params.limit ? params.limit : this.constructor.defaultLimit, - pages: params.limit ? Math.ceil(result.count / params.limit) : 1 + pages }; } diff --git a/tests/mysql-test.js b/tests/mysql-test.js index 0d1c38f..f320091 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -398,6 +398,31 @@ describe('MySQL module', function() { }); }); + it('Should return results and totals with max number of page if try to request a higher number', async function() { + + const originalParams = { someFilter: 'foo', page: 74, limit: 10 }; + const params = { ...originalParams }; + + stubGet.callsFake(() => [{ result: 1 }, { result: 2 }]); + + const result = await mysql.get(dummyModel, params); + + assert.deepEqual(result, [{ result: 1 }, { result: 2 }]); + + testParams(params, originalParams); + + stubGet.callsFake(() => [{ count: 650 }]); + + const resultTotals = await mysql.getTotals(dummyModel); + + assert.deepEqual(resultTotals, { + total: 650, + page: 65, + pageSize: 10, + pages: 65 + }); + }); + }); context('when try to get items with invalid configuration', function() { From 4bb8660318c22c6ae23e84880aec770783121536 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 11 Jul 2019 14:43:24 -0300 Subject: [PATCH 28/35] Fixed wrong scope spelling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9213b6..446a249 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ The codes are the following: ## Usage ```javascript -const Mysql = require('@janniscomercer/mysql'); +const Mysql = require('@janiscommerce/mysql'); const config = { host: 'localhost', From 816db744d9c5925cfcff5e0a22f2fbcaa2bb67d5 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 11 Jul 2019 15:20:31 -0300 Subject: [PATCH 29/35] Added default config in constructor --- lib/mysql.js | 14 +++++++++----- tests/mysql-test.js | 40 ++++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/mysql.js b/lib/mysql.js index 41009a6..a53b91e 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -10,6 +10,10 @@ const Utils = require('../utils'); const CONNECTION_LIMIT = 10; const DEFAULT_LIMIT = 500; +const DEFAULT_HOST = 'localhost'; +const DEFAULT_PORT = 3306; +const DEFAULT_USER = 'root'; +const DEFAULT_PASSWORD = ''; /* QueryBuilder uses Knex Raw in Save and Removed methods * Knex Raw Response with MySql2 module look like this: @@ -43,12 +47,13 @@ class MySQL { * @param {object} config Database configuration. */ constructor(config) { + this.config = { - host: config.host, - user: config.user, - password: config.password, + host: config.host || DEFAULT_HOST, + user: config.user || DEFAULT_USER, + password: config.password || DEFAULT_PASSWORD, database: config.database || null, - port: config.port, + port: config.port || DEFAULT_PORT, connectionLimit: config.connectionLimit || CONNECTION_LIMIT, multipleStatements: true, prefix: config.prefix || '' @@ -244,7 +249,6 @@ class MySQL { let page; // Control that page are not bigger than total pages - // -----------------> HACER TEST DE ESTO PARA EL COVERAGE <------------ if(!params.page) page = 1; else diff --git a/tests/mysql-test.js b/tests/mysql-test.js index f320091..c911c17 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -11,29 +11,33 @@ const MySQL = require('./../index'); const sandbox = sinon.createSandbox(); -describe('MySQL module', function() { - - class Model { - getTable() { - return 'table'; - } +class Model { + getTable() { + return 'table'; + } - addDbName(t) { - return t; - } + addDbName(t) { + return t; + } - static get fields() { - return { - id: true, - superhero: true - }; - } + static get fields() { + return { + id: true, + superhero: true + }; } +} + +describe('MySQL module', function() { - const mysql = new MySQL({}); + let mysql; + let dummyModel; - const dummyModel = new Model(); - dummyModel.dbname = 'dbname'; + beforeEach(() => { + mysql = new MySQL({}); + dummyModel = new Model(); + dummyModel.dbname = 'dbname'; + }); after(() => { sandbox.restore(); From 98fb843b3a904f35032b6f64c93ce7db4f2783d8 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Thu, 11 Jul 2019 15:33:34 -0300 Subject: [PATCH 30/35] Deleted lines not needed. --- lib/mysql.js | 1 + tests/mysql-test.js | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/mysql.js b/lib/mysql.js index a53b91e..f5e90bf 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -160,6 +160,7 @@ class MySQL { /** * Update a row + * @param {instance} model Model instance * @param {object} values - The value to change * @param {object} filters - The filters * @returns {Promise} number of rows updated diff --git a/tests/mysql-test.js b/tests/mysql-test.js index c911c17..c934339 100644 --- a/tests/mysql-test.js +++ b/tests/mysql-test.js @@ -12,9 +12,6 @@ const MySQL = require('./../index'); const sandbox = sinon.createSandbox(); class Model { - getTable() { - return 'table'; - } addDbName(t) { return t; @@ -36,7 +33,6 @@ describe('MySQL module', function() { beforeEach(() => { mysql = new MySQL({}); dummyModel = new Model(); - dummyModel.dbname = 'dbname'; }); after(() => { From 7ac12ca232d350af20b412f104582c1e24c313dc Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 12 Jul 2019 16:42:05 -0300 Subject: [PATCH 31/35] Fixed links, added documentation to README.md, updated CHANGELOG --- CHANGELOG.md | 5 +++-- README.md | 35 +++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5766d88..5adc736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Changed modules files folder into *"lib/"* - Use `Query Builder` Insert, Save, Update, Remove new functions. -- +- moved `utils/` to `lib/` +- Fixed links in `README.md` ### Removed - `Query Builder` moved to an independent package -- `end` function. \ No newline at end of file +- `end` function. diff --git a/README.md b/README.md index 446a249..29e92fe 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ const config = { - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. - `values`: *type* `object`, *key*: field to change, *value*: new value. - - `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). + - `filters`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Filters.md). - **Returns**, `number` of the quantity of rows were updated correctly. @@ -71,22 +71,25 @@ const config = { - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. - `parametres`: *type* `OBJECT`, with the following `keys` to make the query: - * `fields`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Fields.md). - * `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). - * `joins`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Joins.md). - * `order`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Orders.md). - * `group`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Groups.md). - * `limit`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Pagination.md). - * *special functions*: Learn [More](https://github.com/janis-commerce/query-builder/docs/Special-functions.md). + * `fields`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Fields.md). + * `filters`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Filters.md). + * `joins`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Joins.md). + * `order`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Orders.md). + * `group`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Groups.md). + * `limit`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Pagination.md). + * *special functions*: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Special-functions.md). - **Returns**, `Array` of `objects` of *rows* founds. +* `getTotals(model)` **ASYNCHRONOUS**, Get the totals of the items from the latest `get` operation with pagination. + - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. + - **Returns**, `Object` with the total count, page size, pages and selected page. * `remove(model, parametres)` **ASYNCHRONOUS**, Remove rows in the database. - `model`: a Model instance with the *database*, *tables*, *fields*, *joins* and other data. - `parametres`: *type* `OBJECT`, with the following `keys` to make the changes: - - `filters`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Filters.md). - - `joins`: Learn [More](https://github.com/janis-commerce/query-builder/docs/Joins.md). + - `filters`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Filters.md). + - `joins`: Learn [More](https://github.com/janis-commerce/query-builder/blob/master/docs/Joins.md). - **Returns**, `number` of the quantity of rows were removed correctly. @@ -305,6 +308,18 @@ try { ... ] */ + + // getTotals + const totals = await mysql.getTotals(movieModel); + + /* Example return + { + page: 1, + limit: 500, + pages: 1, + total: 7 + } + */ console.log('Movies ', movieResponse); // Print in Console } catch(error) { From 0385ca6fe5728505c1ffe9dd0694de8671827162 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 12 Jul 2019 16:42:42 -0300 Subject: [PATCH 32/35] Moved utils to lib --- lib/mysql.js | 2 +- {utils => lib/utils}/index.js | 0 package.json | 3 +-- tests/utils-test.js | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) rename {utils => lib/utils}/index.js (100%) diff --git a/lib/mysql.js b/lib/mysql.js index f5e90bf..3fde116 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -6,7 +6,7 @@ const logger = require('@janiscommerce/logger'); const QueryBuilder = require('@janiscommerce/query-builder'); const MySQLError = require('./mysql-error'); -const Utils = require('../utils'); +const Utils = require('./utils'); const CONNECTION_LIMIT = 10; const DEFAULT_LIMIT = 500; diff --git a/utils/index.js b/lib/utils/index.js similarity index 100% rename from utils/index.js rename to lib/utils/index.js diff --git a/package.json b/package.json index ab492cc..02529bb 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "husky": "^2.4.1" }, "files": [ - "lib/", - "utils" + "lib/" ] } diff --git a/tests/utils-test.js b/tests/utils-test.js index e4b2c40..b0d9623 100644 --- a/tests/utils-test.js +++ b/tests/utils-test.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const Utils = require('./../utils'); +const Utils = require('./../lib/utils'); /** Setup * */ From dab6c99b9bcb78b489d32fd91dee5258a8bcb5b2 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 12 Jul 2019 16:48:05 -0300 Subject: [PATCH 33/35] Changed Query-Builder dependency to npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02529bb..b8e6567 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "homepage": "https://github.com/janis-commerce/mysql#readme", "dependencies": { "@janiscommerce/logger": "^1.0.2", - "@janiscommerce/query-builder": "file ./../../query-builder", + "@janiscommerce/query-builder": "@janniscomerce/query-builder", "knex": "^0.16.5", "mysql2": "1.5.2" }, From 5ec7f0538a970570e21b3ef1927f226908637e41 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 12 Jul 2019 16:51:05 -0300 Subject: [PATCH 34/35] Fixed Query-Builder dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8e6567..fd24e87 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "homepage": "https://github.com/janis-commerce/mysql#readme", "dependencies": { "@janiscommerce/logger": "^1.0.2", - "@janiscommerce/query-builder": "@janniscomerce/query-builder", + "@janiscommerce/query-builder": "^1.0.0", "knex": "^0.16.5", "mysql2": "1.5.2" }, From f4f074f59ad4c0931db74bcbca4d0ac27ba26840 Mon Sep 17 00:00:00 2001 From: gastonpereyra Date: Fri, 12 Jul 2019 16:56:20 -0300 Subject: [PATCH 35/35] Package-lock updated --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 835464b..d9732bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -131,7 +131,7 @@ } }, "@janiscommerce/query-builder": { - "version": "file:../query-builder", + "version": "1.0.0", "requires": { "@janiscommerce/logger": "^1.0.2" }